2018ACM/ICPC南京站网络赛E AC Challenge(补题)

题目链接

题目大意是有n道题,给出每道题的ai、bi和前置要求完成的题目编号,在t时刻完成编号为i的题获得分数为t*ai+bi(可以为负),求每分钟完成一道题且不要求所有题都完成的情况下的最大分数。

虽然第一眼就觉得是状压,也想到状压该怎么转移,但是可能因为状压的题写的太少,感觉限制太多了有环什么的,不自觉就想偏到在拓扑的同时dp,然后脑子一抽写成了暴搜...(全排列的复杂度是真的骚QAQ)

赛后看了聚聚的题解,呃,可以说是非常暴力了。。。

还有一点,亲测这道题的数据并没有环,虽然其实有没有环写起来差不多,但对我这种蒟蒻不太友好。。。

题目解法就是将状态用二进制数表示,二进制形式下如果第i位为1则说明已完成该题,如果没有前置题目的要求,那么直接枚举为1的所有位,代表若该题是已完成的题目中的最后一题,取所有情况的最大值即可。
有了前置题目的要求后,意味着有些状态并不是合法的状态,比如完成1需要先完成2,那么就不会出现完成了1却没有完成2的状态,直接将这些限制建边,枚举状态时遍历状态中完成的题目的所有边,即可判断该状态是否合法,对于合法的状态,就像没有限制的情况下一样转移就好了。
如果本题还有环的话,那么还需要考虑一种情况:如完成1需要先完成2,完成2需要先完成1,形成了环,那么两者应该都完不成,可是在两者都完成了的状态里,按前面的判断方法会被认为是合法的,因此我们需要将dp数组初始化为负无穷,这样这些不存在的状态就不会影响最后的结果了。当然也可以特判一下,只有转移到当前状态的状态的dp值不为负无穷才更新当前状态,也可以确保得到正确答案。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N = 22;
const int mod=1e9+7;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,a[N],b[N],si;
vector<int> e[N];
bool vis[N];
const int S=1<<20;
ll dp[S],ans=0;
int x;
int main(){
	memset(dp,-INF,sizeof(dp));
	dp[0]=0;
	scanf("%d",&n);
	For(i,1,n){
		scanf("%d %d %d",&a[i],&b[i],&si);
		For(j,1,si){
			scanf("%d",&x);
			e[i].push_back(x);
		}
	}
	int s=(1<<n)-1;
	For(i,1,s){
		bool flag=0;
		For(j,1,n){
			if(((1<<(j-1))&i)==0) continue;
			For(k,0,(int)e[j].size()-1){
				if(((1<<(e[j][k]-1))&i)==0){
					flag=1;break;
				}
			}
			if(flag) break;
		}
		if(flag) continue;
		int tmp=i,tt=0;
		while(tmp){
			if(tmp&1) tt++;
			tmp>>=1;
		}
		For(j,1,n){
			if(((1<<(j-1))&i)==0) continue;
			if(dp[i-(1<<(j-1))]!=-INF)
			dp[i]=max(dp[i],dp[i-(1<<(j-1))]+tt*a[j]+b[j]);
		}
		ans=max(ans,dp[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

时间:15ms

猜你喜欢

转载自blog.csdn.net/qq_38515845/article/details/82292223
今日推荐