A
题目描述: 给一个 n 个节点 m 条边的无向图, 有 k 轮操作, 每轮操作是选择尽量多的边删除, 如果有多种方案, 那么选择边权和最大的那个, 但是要求删除的边中不存在环.对于每条边, 输出它在第几次操作被删除, 如果这条边最后都没有被删除那么输出 0.
先画个图来看看想法
红色的为这一轮所选删除的边
黄色为第2轮所删除的边。
最后剩了边权为1的这条边。
我们再观察一下,每次删除的就是一棵树,因为树上再加一条边就是环了,所以其实就是每次找最大生成树。
那应该怎么找呢?
本蒟蒻只搞出了暴力枚举每轮k,每轮中库鲁斯卡尔找最大生成树。
#include<bits/stdc++.h>
using namespace std;
#define num ch-'0'
void get(int &res)
{
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
const int N=1e3+5,M=3e5+5,K=1e4+5;
int far[N][K],n,m,k,h[N],Num[N][N];
struct node
{
int x,y,w,id;
}e[M];
inline bool comp(node u,node v)
{
return u.w>v.w;
}
inline int getfar(int x,int t)
{
return far[x][t]==x? x:far[x][t]=getfar(far[x][t],t);
}
int main()
{
get(n);get(m);get(k);
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++) far[i][j]=i;
for(int i=1;i<=m;i++)
{
get(e[i].x);get(e[i].y);get(e[i].w);
e[i].id=i;
}
sort(e+1,e+m+1,comp);
for(int i=1;i<=m;i++)
{
int u=e[i].x,v=e[i].y;
int t=Num[u][v]+1,f1=getfar(u,t),f2=getfar(v,t);
while(f1==f2 && t<k)
{
t++;
f1=getfar(u,t);
f2=getfar(v,t);
}
if(f1==f2) continue;
h[e[i].id]=t;
far[f2][t]=f1;
Num[u][v]=Num[v][u]=t;
}
for(int i=1;i<=m;i++)
{
cout<<h[i]<<endl;
}
}
然后居然A了70,电脑太好了吧
好了,下面是正解。(来自于我们机房的大佬)
也是用最大生成树,但我们开并查集时用far[N][K]来维护每一轮的并查集。
假如我已经在第一轮将2个点n,m合在一起了,在继续枚举,发现新的2个点a,b的祖先在第一轮相等,说明这2个点第一轮已经合了,就跳到下一轮,继续判断。
我们发现这个过程可以优化,不需要每次从第一轮开始,如果这两个点上个状态是第几轮,那么这次这2个点就肯定要在上一个的轮数+1,因为上一轮我们就已经跳了到,这一轮肯定也要跳,不如直接加。(太巧妙了) 所以其中最重要的是int t=past[u][v]+1,如果改为int t=1那时间复杂度就会和上面的代码差不多。
#include<bits/stdc++.h>
using namespace std;
#define num ch-'0'
void get(int &res)
{
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
const int N=1005,M=3e5+5,K=1e4+5;
int n,m,k,far[N][K],past[N][N];
int vis[M];
struct node
{
int x,y,w,id;
}e[M];
inline comp(node x,node y)
{
return x.w>y.w;
}
inline int getfar(int x,int t)
{
return far[x][t]==x?x:far[x][t]=getfar(far[x][t],t);
}
int main()
{
get(n);get(m);get(k);
for(int i=1;i<=m;i++)
{
get(e[i].x);get(e[i].y);get(e[i].w);
e[i].id=i;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
far[i][j]=i; //初始化,初始化,初始化
sort(e+1,e+m+1,comp);//贪心
for(int i=1;i<=m;i++)
{
int u=e[i].x,v=e[i].y;
int t=past[u][v]+1,f1=getfar(u,t),f2=getfar(v,t);
//因为是线性的,这2个点可以直接从过去状态开始,减少了大量的循环次数
while(f1==f2 && t<k)//在这个并查集中祖先相同,找下一个
{
t++;
f1=getfar(u,t);
f2=getfar(v,t);
}
if(f1==f2) continue;
vis[e[i].id]=t;
far[f2][t]=f1;
past[u][v]=past[v][u]=t;
}
for(int i=1;i<=m;i++)
cout<<vis[i]<<endl;
return 0;
}
B
题目描述:有一个n×n的黑白棋盘. 你需要用一些操作将整个棋盘变成全黑.一次操作首先选择一行i, 一列j, 记c1,c2,·· ·,cn为(i,1),(i,2),·· ·,(i,n) 的颜色. 之后将(1,j),(2,j),·· ·,(n,j)的颜色对应涂成c1,c2,·· ·,cn.求最少的操作次数. 如果无法成功, 输出−1.其中’#‘表示黑色,’.'表示白色.
我们多画点图可以发现:
1.当且仅当没有黑色的时候,做不出,输出-1;
2.只要把一行全部转换为了黑的,那么就能直接将全部列都完成,其中只需特判一下是否一列都是黑,在最后减掉即可;
3.为了做出一行为黑,我们又发现,如果在从左上到右下的对角线上的黑色,可以直接在它的行上造,所以这种情况的就这一行的白色数;
4.如果在其他位置的黑色,那么只能在不在它的其他行上造黑色,所以这个情况就是对应行的白色数+1;
综上,结果等于ans+n-cnt(造一行黑色的最小代价+去造每一列-一列全是黑的情况)
#include<bits/stdc++.h>
using namespace std;
const int N=505;
int n,num[N];
bool w=1,list1[N],list2[N];
//特判是否全白,是否一列全是黑的,是否一列有黑的
int main()
{
char x;
scanf("%d",&n);
memset(list1,1,sizeof(list1));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>x;
if(x=='#')
{
w=0;
list2[j]=1;
}
else
{
num[i]++;
list1[j]=0;
}
}
if(w)
{
cout<<-1;
return 0;
}
int ans=0x3f3f3f3f,cnt=0;
for(int i=1;i<=n;i++)
{
if(list2[i]) ans=min(ans,num[i]);
else ans=min(ans,num[i]+1);
if(list1[i]) cnt++;
}
cout<<n+ans-cnt;
return 0;
}
其余2题本蒟蒻不会,会了就更qwq 。