「学习笔记」 基础DP-Summar Vacation Trainning Day1

Ehab and the Expected GCD Problem

比较容易发现第一个元素所拥有的质因子个数最多,且往后的元素依次最多剔除1个质因子时最优。
最小的质因子就是2和3了,如果等于5,那么可以变成成 \(2^2\) 更优,其他同理。
并且3的数量最多为1。
设第一个元素为 m=\(2^x3^y\),dp[i][x][y] 表示前 i 个元素的 gcd 为 \(2^x3^y\) 的方案数。
设 v[x][y] 表示 \(\frac{\lfloor n\rfloor}{2^x3^y}\)
可以发现转移有三种:

\(dp_{i,j,k}=\begin{cases}dp_{i-1,j,k}\times\max(0,v[j][k]-i+1)\\dp_{i-1,j+1,k}\times(v[j][k]-v[j+1][k])\\dp_{i-1,j,k+1}\times(v[j][k]-v[j][k+1])\end{cases}\)

分别对应 gcd 不减,减去1个2,减去1个3。
这道题给我们的思考就是,当找到题目要求的某个条件如何满足时,可能顺着这个条件就可以把整道题的转移方案考虑出来。
Code:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXN=1e6+1;
const int MAXK=21;
const int Mod=1e9+7;
int n,m2,m3;
int dp[MAXN][MAXK][2];
int v[20][2];
int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a;
		a=a*a;b>>=1;
	}
	return res;
}
int main(){
	scanf("%d",&n);
	if(n==1){
		printf("1");return 0;
	}
	for(int i=0;i<=20;i++){
		for(int j=0;j<=1;j++){
			v[i][j]=n/(ksm(2,i)*ksm(3,j));
		}
	}
	int op=n;
	while(op/2>0){
		op/=2;m2++;
	}
	if(ksm(2,m2-1)*3<=n) m3=1;
	dp[1][m2][0]=1;
	if(m3) dp[1][m2-1][1]=1;
	for(int i=2;i<=n;i++){
		for(int j=0;j<=m2;j++){
			for(int k=0;k<=m3;k++){
				dp[i][j][k]=1ll*dp[i-1][j][k]*max(0,(v[j][k]-i+1))%Mod;
				if(j+1<=m2){dp[i][j][k]+=1ll*dp[i-1][j+1][k]*(v[j][k]-v[j+1][k])%Mod;dp[i][j][k]=((dp[i][j][k]%Mod)+Mod)%Mod;}
				if(k+1<=m3){dp[i][j][k]+=1ll*dp[i-1][j][k+1]*(v[j][k]-v[j][k+1])%Mod;dp[i][j][k]=((dp[i][j][k]%Mod)+Mod)%Mod;}
			}
		}
	}
	printf("%d",((dp[n][0][0]%Mod)+Mod)%Mod);
	return 0;
}

注意要卡下空间。

Three Servers

很神奇啊。
考虑暴力背包,f[i][j] 表示是否能达到前两个背包总和分别为 i,j。
想到如果1个背包的值大于4030,那么必定分一些去另外两个更优,所以 M 上界设成4030即可。
这样转移是 \(\Omicron(NM^2)\)......不太行哩。
然后非常神奇的,发现 \(k_i\leq30\),把每种 k 值的数量记录成 sz[k]。
同时用一个辅助数组 g[i][j] 表示达到前两个背包总和分别为 i,j 时剩下的 k 的数量的最大值。
如果这个值大于等于0,就代表 f[i][j]=true。
这样我们牺牲了1个 \(M^2\) 的空间,但是把时间解决为了 \(kM^2\)
因为要输出方案,所以记录一下转移用到的 f[i][j] 和加入的 k 值即可。
我**地把值哈希了一下...
Code:

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int MAXN = 400;
const int MAXM = 4100;
const int MAXK = 30;
int n, g[MAXM + 5][MAXM + 5], sum, res, pt, jj = 4101, maxm;
bool f[MAXM + 5][MAXM + 5];
vector<int> ans[5], num[32];
int lst[2][MAXM + 5][MAXM + 5];
void calc(int now) {
    if (!now)
        return;
    int x = now / jj, y = now - x * jj;
    int xx = lst[0][x][y] / jj, yy = lst[0][x][y] - xx * jj;
    if (x > xx) {
        ans[1].push_back(num[lst[1][x][y]][num[lst[1][x][y]].size() - 1]);
        num[lst[1][x][y]].pop_back();
    } else {
        ans[2].push_back(num[lst[1][x][y]][num[lst[1][x][y]].size() - 1]);
        num[lst[1][x][y]].pop_back();
    }
    calc(lst[0][x][y]);
    return;
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        sum += x;
        num[x].push_back(i);
    }
    f[0][0] = 1;
    res = sum;
    maxm = sum / 3 + 30;
    for (int i = 1; i <= 30; i++) {
        if (!num[i].size())
            continue;
        for (int j = 0; j <= maxm; j++) {
            for (int k = 0; k <= maxm; k++) {
                g[j][k] = -1;
                if (f[j][k])
                    g[j][k] = num[i].size();
            }
        }
        for (int j = 0; j <= maxm; j++) {
            for (int k = 0; k <= maxm; k++) {
                if (k >= i) {
                	if (g[j][k - i] - 1 > g[j][k])
                		lst[0][j][k] = j * jj + (k - i), lst[1][j][k] = i;
                    g[j][k] = max(g[j][k], g[j][k - i] - 1);
                }
                if (j >= i) {
                	if (g[j - i][k] - 1 > g[j][k])
                		lst[0][j][k] = (j - i) * jj + k, lst[1][j][k] = i;
                    g[j][k] = max(g[j][k], g[j - i][k] - 1);
                }
            }
        }
        for (int j = 0; j <= maxm; j++) {
            for (int k = 0; k <= maxm; k++) {
                if (g[j][k] >= 0)
                    f[j][k] = 1;
            }
        }
    }
    for (int i = 0; i <= maxm; i++) {
        for (int j = 0; j <= maxm; j++) {
            if (f[i][j]) {
                if (max(i, max(j, sum - i - j)) - min(i, min(j, sum - i - j)) < res) {
                    res = max(i, max(j, sum - i - j)) - min(i, min(j, sum - i - j));
                    pt = i * jj + j;
                }
            }
        }
    }
    printf("%d\n", res);
    calc(pt);
    for (int i = 1; i <= 30; i++) {
        while (num[i].size()) {
            ans[3].push_back(num[i][num[i].size() - 1]);
            num[i].pop_back();
        }
    }
    for (int i = 1; i <= 3; i++) {
        printf("%d ", ans[i].size());
        for (int j = 0; j < ans[i].size(); j++) printf("%d ", ans[i][j]);
        printf("\n");
    }
    return 0;
}
/*
10
23 1 8 7 6 30 8 27 13 23
*/ 

Hero meet devil

这道题还要抽象一点。
发现|S|极小。
设记录当前的 LCP 是在 S 的哪些位置上,下一个加入的字符是 x(\(x\in{A,C,G,T}\)) 时新的状态。
记录状态可以使用状压,用 dp[s][x] 表示。
有了这个,再设 f[i][s] 表示构造出前 i 个数后状态为 s 的方案数。
然后就很方便的转移了,已经跟递推差不多了。
Code:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define ll long long
const int Mod=1e9+7;
const int N=1e3+1;
const int MAXK=15;
const int M=1<<15;
int len,m,t,a[MAXK+5],zhu[M][5],dp[N+5][M+5],va[2][20],ans[20];
char s[MAXK+1];
int kk(char c){
	if(c=='A') return 1;
	if(c=='C') return 2;
	if(c=='G') return 3;
	if(c=='T') return 4;
}
int dp1(int v,int c){
	memset(va,0,sizeof(va));
	for(int i=1;i<=len;i++){va[0][i]=1&(v>>(i-1));va[0][i]+=va[0][i-1];}
	for(int i=1;i<=len;i++){
		int tmp=0;
		va[1][i]=max(va[0][i],va[1][i-1]);
		if(a[i]==c){
			va[1][i]=max(va[1][i],va[0][i-1]+1);
		}
	}
	int res=0;
	for(int i=1;i<=len;i++) res+=(va[1][i]-va[1][i-1])*(1<<(i-1));
	return res;
}
int num(int v){
	int res=0;
	for(int i=1;i<=MAXK;i++) res+=((v>>(i-1))&1);
	return res;
}
int main(){
	scanf("%d",&t);
	while(t--){
		memset(dp,0,sizeof(dp));memset(zhu,0,sizeof(zhu));memset(ans,0,sizeof(ans));
		scanf("%s",s+1);
		len=strlen(s+1);
		scanf("%d",&m);
		for(int i=1;i<=len;i++) a[i]=kk(s[i]);
		for(int i=0;i<(1<<len);i++){
			for(int j=1;j<=4;++j){
				zhu[i][j]=dp1(i,j);
			} 
		} 
		dp[0][0]=1;
		for(int i=1;i<=m;i++){
			for(int j=0;j<(1<<len);++j){
				for(int k=1;k<=4;++k){
					dp[i][zhu[j][k]]=(dp[i][zhu[j][k]]+dp[i-1][j])%Mod;
				}
			}
		}
		for(int i=0;i<(1<<len);i++) ans[num(i)]=(ans[num(i)]+dp[m][i])%Mod;
		for(int i=0;i<=len;i++) printf("%d\n",ans[i]);
	}
	return 0;
}

posted @ 2022-07-01 21:09  StranGePants  阅读(22)  评论(0编辑  收藏  举报