Codeforces 1354F. Summoning Minions 题解

题目大意:你有\(n\)张卡牌,你的桌面上最多只可容纳\(k\)张卡牌,每一张卡牌放到桌面上时有一个初始的等级\(a_i\),并且在放到桌面上的时刻会给所有当前桌面上除该卡牌之外所有的卡牌等级全部加上\(b_i\),中途你可以销毁桌上的一些卡牌,求任意一种方案使得最后桌上所有卡牌的等级之和最大。多组数据。\(T\text{(数据组数)} \le 75,1 \le k \le n \le 75\)

题目链接:F. Summoning Minions


题解:

Solution 1

暴力枚举全排列,时间复杂度\(O(T \cdot n!)\)

Solution 2

由贪心很容易知道桌上肯定放满\(k\)张卡牌时最优,考虑如果固定了桌上最后的\(k\)卡牌,求出最大方案。可以将这\(k\)张卡牌的\(b_i\)值从小到大排序,然后先取前\(k-1\)张挨个放在桌上,再将剩下的\(n-k\)张不是最后在桌上的卡牌放一个销毁一个,再将最后一张卡牌放在桌上。若枚举最后的\(k\)张卡牌,时间复杂度\(O(T\cdot n\binom{n}{k})\)

Solution 3

因为我们发现对于固定任意\(k\)张卡牌操作顺序是不变的,所以可以建立二分图模型,左侧是卡牌,右侧是位置,然后中间连上边,边权给什么值很简单,这里就不列了。用 Dinic 算法来求解,时间复杂度\(O(\text{不可过})\)

#include <cstdio>
#include <cstring>
void read(int &a){
	a=0;
	char c=getchar();
	while(c<'0'||c>'9'){
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		a=(a<<1)+(a<<3)+(c^48);
		c=getchar();
	}
}
template<typename Elem_1,typename Elem_2>
Elem_1 min(Elem_1 a,Elem_2 b){
	return a<b?a:b;
}
const int Maxn=200;
const int Maxm=Maxn*Maxn*2;
const int Inf=0x3f3f3f3f;
struct Edge{
	int to,nxt,val;
	int cap,flow;
}edge[Maxm<<1|5];
int head[Maxn+5],tot;
void init(){
	memset(head,0,sizeof head);
	tot=0;
}
void unuse_add_edge(int from,int to,int cap,int val){
	edge[++tot].to=to;
	edge[tot].nxt=head[from];
	edge[tot].val=val;
	edge[tot].cap=cap;
	edge[tot].flow=0;
	head[from]=tot;
}
void add_edge(int from,int to,int cap,int val){
	unuse_add_edge(from,to,cap,val);
	unuse_add_edge(to,from,0,-val);
}
int n,m;
int a[Maxn+5],b[Maxn+5];
int qu[Maxn+5],qu_f,qu_t;
int dis[Maxn+5];
bool vis[Maxn+5];
bool in_q[Maxn+5];
int cur_f[Maxn+5];
int S,T;
bool Dinic_bfs(){
	memset(dis,0x3f,sizeof dis);
	qu_f=1,qu_t=0;
	qu[++qu_t]=S;
	in_q[S]=1;
	dis[S]=0;
	while(qu_f!=qu_t+1){
		int u=qu[qu_f++];
		in_q[u]=0;
		qu_f%=(Maxn+1);
		for(int i=head[u];i;i=edge[i].nxt){
			int v=edge[i].to;
			if(edge[i].flow==edge[i].cap){
				continue;
			}
			if(dis[u]+edge[i].val<dis[v]){
				dis[v]=dis[u]+edge[i].val;
				if(!in_q[v]){
					in_q[v]=1;
					qu_t++;
					qu_t%=(Maxn+1);
					qu[qu_t]=v;
				}
			}
		}
	}
	return dis[T]!=Inf;
}
int Dinic_dfs(int u,int flow,int &min_cost){
	if(u==T||flow==0){
		return flow;
	}
	vis[u]=1;
	int sum=0;
	for(int i=cur_f[u];i;i=edge[i].nxt){
		cur_f[u]=i;
		int v=edge[i].to;
		if(dis[v]!=dis[u]+edge[i].val||vis[v]||edge[i].flow==edge[i].cap){
			continue;
		}
		int op=min(flow-sum,edge[i].cap-edge[i].flow),f;
		if(f=Dinic_dfs(v,op,min_cost)){
			edge[i].flow+=f;
			edge[((i-1)^1)+1].flow-=f;
			min_cost+=edge[i].val*f;
			sum+=f;
			if(sum==flow){
				break;
			}
		}
	}
	vis[u]=0;
	if(sum==0){
		dis[u]=Inf;
	}
	return sum;
}
int Dinic(int &min_cost){
	int ans=0;
	min_cost=0;
	while(Dinic_bfs()){
		memcpy(cur_f,head,sizeof head);
		ans+=Dinic_dfs(S,Inf,min_cost);
	}
	return ans;
}
int output[Maxn+5],id[Maxn+5];
int main(){
	int Temp;
	scanf("%d",&Temp);
	int ans;
	while(Temp--){
		init();
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			scanf("%d%d",&a[i],&b[i]);
		}
		S=n+n+1;
		T=n+n+2;
		for(int i=1;i<=n;i++){
			add_edge(S,i,1,0);
			for(int j=1;j<m;j++){
				add_edge(i,j+n,1,-a[i]-b[i]*(j-1));
			}
			for(int j=m;j<n;j++){
				add_edge(i,j+n,1,-b[i]*(m-1));
			}
			add_edge(i,n+n,1,-a[i]-b[i]*(m-1));
		}
		for(int i=1;i<=n;i++){
			add_edge(i+n,T,1,0);
		}
		Dinic(ans);
		for(int i=1;i<=n;i++){
			int num=n;
			for(int j=head[i];j;j=edge[j].nxt){
				int v=edge[j].to;
				if(edge[j].flow==1){
					output[i]=num;
					break;
				}
				num--;
			}
			id[num]=i;
		}
		int len=0;
		for(int i=1;i<=n;i++){
			if(i<m){
				len++;
			}
			else if(i==n){
				len++;
			}
			else{
				len+=2;
			}
		}
		printf("%d\n",len);
		for(int i=1;i<=n;i++){
			if(i<m){
				printf("%d ",id[i]);
			}
			else if(i==n){
				printf("%d ",id[i]);
				
			}
			else{
				printf("%d %d ",id[i],-id[i]);
			}
		}
		puts("");
	}
	return 0;
}

Solution 4

对于 Solution 3 的求解过程采用 KM 算法,时间复杂度\(O(T\cdot n^3)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=200;
const int Maxm=Maxn*Maxn*2;
const int Inf=0x3f3f3f3f;
int n,m;
int a[Maxn+5],b[Maxn+5];
int mp[Maxn+5][Maxn+5];
int ex_a[Maxn+5],ex_b[Maxn+5];
bool vis_a[Maxn+5],vis_b[Maxn+5];
int match[Maxn+5];
int slack[Maxn+5];
bool dfs(int u){
	vis_a[u]=1;
	for(int i=1;i<=n;i++){
		if(vis_b[i]){
			continue;
		}
		int gap=ex_a[u]+ex_b[i]-mp[u][i];
		if(gap==0){
			vis_b[i]=1;
			if(match[i]==-1||dfs(match[i])){
				match[i]=u;
				return 1;
			}
		}
		else{
			slack[i]=min(slack[i],gap);
		}
	}
	return 0;
}
int main(){
	int T;
	scanf("%d",&T);
	int ans;
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			scanf("%d%d",&a[i],&b[i]);
		}
		memset(mp,-1,sizeof mp);
		for(int i=1;i<=n;i++){
			for(int j=1;j<m;j++){
				mp[i][j]=a[i]+b[i]*(j-1);
			}
			for(int j=m;j<n;j++){
				mp[i][j]=b[i]*(m-1);
			}
			mp[i][n]=a[i]+b[i]*(m-1);
			ex_a[i]=mp[i][n];
		}
		memset(match,-1,sizeof match);
		memset(ex_b,0,sizeof ex_b);
		for(int i=1;i<=n;i++){
			memset(slack,0x3f,sizeof slack);
			while(1){
				memset(vis_a,0,sizeof vis_a);
				memset(vis_b,0,sizeof vis_b);
				if(dfs(i)){
					break;
				}
				int d=Inf;
				for(int j=1;j<=n;j++){
					if(!vis_b[j]){
						d=min(d,slack[j]);
					}
				}
				for(int j=1;j<=n;j++){
					if(vis_a[j]){
						ex_a[j]-=d;
					}
					if(vis_b[j]){
						ex_b[j]+=d;
					}
					else{
						slack[j]-=d;
					}
				}
			}
		}
		printf("%d\n",m+(n-m)*2);
		for(int i=1;i<=n;i++){
			if(i<m){
				printf("%d ",match[i]);
			}
			else if(i==n){
				printf("%d ",match[i]);
			}
			else{
				printf("%d %d ",match[i],-match[i]);
			}
		}
		puts("");
	}
	return 0;
}

Solution 5

考虑动态规划,先将\(b_i\)从小到大排序,这样的话 DP 在桌上的卡片时就可以决定它的位置了。设\(f_{i,j}\)表示当前决策到第\(i\)个位置,桌上已经确定了\(j\)张卡牌的最大价值,转移方程显然,然后构造方案输出即可,时间复杂度\(O(T\cdot n^2)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn=75;
const int Inf=0x3f3f3f3f;
int n,m;
int a[Maxn+5],b[Maxn+5];
int f[Maxn+5][Maxn+5],last[Maxn+5][Maxn+5];
int id[Maxn+5];
bool chos[Maxn+5];
bool cmp(int p,int q){
	return b[p]<b[q];
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			scanf("%d%d",&a[i],&b[i]);
			id[i]=i;
		}
		sort(id+1,id+1+n,cmp);
		memset(f,-0x3f,sizeof f);
		memset(last,0,sizeof last);
		memset(chos,0,sizeof chos);
		f[0][0]=0;
		for(int i=1;i<=n;i++){
			for(int j=0;j<=m&&j<=i;j++){
				if(f[i-1][j]!=-Inf){
					f[i][j]=f[i-1][j]+b[id[i]]*(m-1);
					last[i][j]=0;
				}
				if(j>0){
					if(f[i-1][j-1]!=-Inf){
						if(f[i-1][j-1]+a[id[i]]+b[id[i]]*(j-1)>f[i][j]){
							f[i][j]=f[i-1][j-1]+a[id[i]]+b[id[i]]*(j-1);
							last[i][j]=1;
						}
					}
				}
			}
		}
		for(int i=n,j=m;i>0;i--){
			if(last[i][j]==1){
				chos[id[i]]=1;
				j--;
			}
		}
		printf("%d\n",m+(n-m)*2);
		int num=0;
		for(int i=1;i<=n;i++){
			if(chos[id[i]]){
				num++;
				if(num==m){
					num=id[i];
					break;
				}
				printf("%d ",id[i]);
			}
		}
		for(int i=1;i<=n;i++){
			if(!chos[i]){
				printf("%d %d ",i,-i);
			}
		}
		printf("%d\n",num);
	}
	return 0;
}
posted @ 2020-05-18 23:44  with_hope  阅读(505)  评论(0编辑  收藏  举报