2021CCPC女生赛 C. 连锁商店(思维+状压DP)

链接

题意:

给出N个节点,给出M条边,我们只能从低节点跳到高节点。然后给出N个节点属于的公司,给出第i个公司第一次到给多少奖金。问从节点1开始,分别到达节点(1~n)最大奖金。

分析:

其实不难想到我们如果当前节点在x,有(a1,a2,a3…)这些节点可以到达节点x,那么我们选取(a1,a2,a3…)所有的状态转移过来就好了,状态表示我们可以用二进制,也可以用字符串数组等等,只要可以表示状态就行。
状态转移有两种状态

  • 一种是在之前出现过,不用再加奖金了,把这个状态加入x
  • 另一种之前没有出现过,加上奖金并将状态改变进入x.

当然这样是会超时的!!!!

所以我们需要优化,如何优化那?
如果连线如下:
2021CCPC女生赛 C. 连锁商店(思维+状压DP)_第1张图片
也就是

1 2 
2 3
3 4
1 4
2 4

那么当我们计算节点4时,我们会跑节点(1,2,3),我们想有什么可以优化的吗?
当然有呀!!!!
计算节点3时我们会跑节点(2),
而计算节点2时我们会跑节点(1),
这样看来,当我们计算节点4时,我们跑了节点3就意味着我们已经跑了节点2,跑了节点2也就意味着跑了节点1,这样我们只需要跑 一个节点3就好了。

是不是很妙,但就是这么妙, 复杂度嘛,
如果算上我优化的思路:

for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			for(int k=i+1;k<j;k++){
				if(dp[i][j]==1&&dp[i][k]==1&&dp[k][j]==1){
					dp[i][j]=0;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(dp[i][j]==1){
				vv[j].push_back(i);
			}
		}
	}

这个就是 O ( n 3 ) O(n^3) O(n3)
然后下面查询节点就是 O ( n ) O(n) O(n)的。
因为经过上面优化他的前驱节点就只有一个,而且每个节点前驱节点都只有一个那么状态也只有一个,那么就是 O ( n ) O(n) O(n)的复杂度呗
所以最终复杂度就是 O ( n 3 + n ) O(n^3+n) O(n3+n)
当然可能还可以把 O ( n 3 ) O(n^3) O(n3)这个优化下。但是对于这个题大可不必。

所以当我们遇到问题时换 个角度想可能就会发现新大陆!!!

ll n, m;
ll a[40],b[40];
vector<pii> v[40];
vector<ll> vv[40];
string str;
ll ans[50];
ll dp[50][50];
void solve()
{       
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin>>a[i],str+="0";
	for(int i=1;i<=n;i++) cin>>b[i];
	
	for(int i=1;i<=m;i++){
		ll x,y;
		cin>> x>>y;
		dp[x][y]=1;
		//vv[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			for(int k=i+1;k<j;k++){
				if(dp[i][j]==1&&dp[i][k]==1&&dp[k][j]==1){
					dp[i][j]=0;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(dp[i][j]==1){
				vv[j].push_back(i);
			}
		}
	}
	ans[1]=b[a[1]];
	ll res=ans[1];
	string tmp=str;
	tmp[a[1]-1]='1';
	v[1].push_back({res,tmp});
	
	for(int i=2;i<=n;i++){		
		ll sum=0;	
		for(int j=0;j<vv[i].size();j++){
			ll pre=vv[i][j];/// pre			
			for(int k=0;k<v[pre].size();k++){
				pii top = v[pre][k];
				res=top.x;
				tmp=top.y;
				if(tmp[a[i]-1]=='0'){
					tmp[a[i]-1]='1';
					res+=b[a[i]];
				}
				v[i].push_back({res,tmp});
				sum=max(sum,res);
			}
		}
		ans[i]=sum;
	}
	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
}

还有一种在提交区发现的:剪枝操作,他是剪去一部分重复的状态就比如:

节点x有状态(1101,1100,1010,1110)那么(1101涵盖了1100那么1100就会被剪去,因为他比较大)
这个剪枝,剪不全,但是也会剪掉很多。

@srly

看起去瞅瞅大佬写的

#include 
#include 
#include 
#include 
using namespace std;
typedef long long ldkyyds;
const int maxn = 40;
int c[maxn], w[maxn];
unordered_map<ldkyyds, int> dp[maxn];
vector<ldkyyds> dis[maxn];
vector<int> vis[maxn];
int maxx[maxn];
void add(int i, ldkyyds ldknb, int val) {/// 位次 , 状态, 奖金
    if (!dp[i].count(ldknb)) {
        dis[i].push_back(ldknb);
    }
    dp[i][ldknb] = max(dp[i][ldknb], val);
    maxx[i] = max(maxx[i], val);
}

void remove(int pos) {
    vector<ldkyyds> diss;
    for (int i = 0; i < dis[pos].size(); i ++) {
        bool flag = false;
        for (int j = 0; j < dis[pos].size(); j ++) {
            if (j == i) {
                continue;
            }
            if ((dis[pos][i] | dis[pos][j]) == dis[pos][j]) {/// 涵盖 i
                flag = true;
                break;
            }
        }
        if (!flag) {///没有被涵盖
            diss.push_back(dis[pos][i]); ///加进去
        }
    }
    dis[pos] = diss;
}
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        cin >> c[i];
    }
    for (int i = 1; i <= n; i ++) {
        cin >> w[i];
    }

    while (m --) {
        int u, v;
        cin >> u >> v;
        vis[v].push_back(u);
    }

    add(1, 1ll<<c[1], w[c[1]]);
    for (int i = 2; i <= n; i ++) {
        for (int j = 0; j < vis[i].size(); j ++) {
            int pos = vis[i][j];///前驱
            for (int t = 0; t < dis[pos].size(); t ++) {///前驱的状态
                if (dis[pos][t] & 1ll<<c[i]) {///有
                    add(i, dis[pos][t], dp[pos][dis[pos][t]]);
                } else {///没有
                    add(i, dis[pos][t] | 1ll<<c[i], dp[pos][dis[pos][t]] + w[c[i]]);
                }
            }
        }
        remove(i);///删除?
    }
    for (int i = 1; i <= n; i ++) {
        cout << maxx[i] << endl;
    }
    return 0;
}

你可能感兴趣的:(思维,DP,c语言,动态规划,开发语言)