题目链接:哆啦A梦传送门
题意:n座城市,m年修复这个国家,每年有如下3种操作之中一个,k表示每一年最多修复城市k座。现在所有的城市和道路都被摧毁了。有m年时间修复这个国家。
操作分三种:
1 x:最多修复与x相连通的城市k座(包括x)。
2 x y:修复城市x与城市y的双向道路。
3 p:接着有p对 (x,y),表示摧毁城市x与城市y的双向道路。
输出:能修复最多多少个城市?接着输出每次1操作时,能修复多少个城市?(字典序要最小)
Sample Input
1
5 6 2
2 1 2
2 1 3
1 1
1 2
3 1 1 2
1 2
Sample Output
3
0 2 1
最多修复3座城市,第四年不修复,第五年修复城市1和城市3,第六年修复城市2。
题解:对于求最多修复多少个城市?我们可以用最大流就可以解决。
只需建模:对于m年中的操作1,我们可以与x相连城市的联通块一起看成是一个点,
然后我们将源点与每个联通块建边,权值为k;
紧接着我们将每个联通块与该联通块中的城市建边,权值为1;
最后我们将每个城市与汇点建边,权值为1。
最后跑个dinic就行了,但是题目还要输出每个操作2时,能修复多少个城市,保证字典序最小。这是单单跑个dinic就解决不了了。但是我们可以给每个源点到每个联通块加一个费用(费用逐级递减),其它费用为0,因为这样我们跑费用流会尽量让后面的操作去修复城市,这样就可以使得字典序最小。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1010;
const int INF=0x3f3f3f3f;
struct edge{
int u,v,flow,cost,next;
edge(){}
edge(int _u,int _v,int _flow,int _cost,int _next){
u=_u;v=_v;flow=_flow;cost=_cost;next=_next;
}
}e[1000000];
int head[N],cnt;
//queue<int> que;
void init()
{
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int x,int y,int z,int c)
{
e[cnt]=edge(x,y,z,c,head[x]);
head[x]=cnt++;
e[cnt]=edge(y,x,0,-c,head[y]);
head[y]=cnt++;
}
bool vis[N];
int dis[N],pre[N];
bool spfa(int s,int t)
{
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
dis[s]=0;
vis[s]=true;
// while(!que.empty()) que.pop();
queue<int> que;
que.push(s);
while(!que.empty())
{
int x=que.front();
que.pop();
vis[x]=false;
for(int i=head[x];i!=-1;i=e[i].next){
if(e[i].flow){
int y=e[i].v;
if(dis[x]+e[i].cost<dis[y]){
dis[y]=dis[x]+e[i].cost;
pre[y]=i;
if(!vis[y]){
que.push(y);
vis[y]=true;
}
}
}
}
}
return pre[t]!=-1;
}
int costflow(int s,int t)///源点x,汇点t,需要的总流x
{
int ret=0;
int max_flow=0;
while(spfa(s,t))
{
int min_flow=INF;
for(int i=pre[t];i!=-1;i=pre[e[i].u]){
min_flow=min(e[i].flow,min_flow);
}
for(int i=pre[t];i!=-1;i=pre[e[i].u]){
e[i].flow-=min_flow;
e[i^1].flow+=min_flow;
}
ret+=dis[t]*min_flow;
max_flow+=min_flow;
}
return max_flow;
}
int n,m,k;
int mp[210][210],tot;
bool flag[510][510];
vector<int> vec[510];
void dfs(int fa,int u)///搜索该连通块相连的城市,存储在vec
{
flag[fa][u]=1;
vec[fa].push_back(u);
for(int i=1;i<=n;i++)
{
if(flag[fa][i]||!mp[u][i]) continue;
dfs(fa,i);
}
}
int main()
{
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
init();
scanf("%d%d%d",&n,&m,&k);
memset(flag,0,sizeof(flag));
memset(mp,0,sizeof(mp));
for(int i=0;i<=m+1;i++)
vec[i].clear();
tot=0;///表示联通块的数量
for(int i=1;i<=m;i++)
{
int op,x,y;
scanf("%d",&op);
if(op==1){
scanf("%d",&x);
tot++;
dfs(tot,x);
}
else if(op==2)
{
scanf("%d%d",&x,&y);
mp[x][y]=mp[y][x]=1;
}
else{
int num;
scanf("%d",&num);
for(int i=1;i<=num;i++){
scanf("%d%d",&x,&y);
mp[x][y]=mp[y][x]=0;
}
}
}
int s=0,t=n+tot+1;
for(int i=1;i<=tot;i++) ///源点到联通块建边,权值为k,费用递减
{
add(s,i,k,m-i);
}
for(int i=1;i<=n;i++) ///每个城市与汇点建边,权值为1,费用为0
add(i+tot,t,1,0);
for(int i=1;i<=tot;i++)
{
int len=vec[i].size();
for(int j=0;j<len;j++){ ///将每个联通块与该联通块内的城市建边,权值为1,费用为0
add(i,vec[i][j]+tot,1,0);
}
}
printf("%d\n",costflow(s,t)); ///跑一个费用流
for(int i=1;i<2*tot-1;i+=2){ ///输出每条源点到各个联通块的反向流量边(表示该边用了多少)
printf("%d ",e[i].flow);
}
printf("%d\n",e[2*tot-1].flow);
}
return 0;
}