dp训练题解

训练链接

CF101E

题目链接

点击打开链接

题目解法

朴素的 d p dp dp 很好写,但发现难以得到最优路径
考虑对于 ( x , y ) (x,y) (x,y) 的转移方式只有两种,可以想到用 b i t s e t bitset bitset 来维护转移,这样可以很节约空间
但我们发现开 n ∗ n n*n nn b i t s e t bitset bitset 是不够的,可以考虑用时间来代替空间
于是可以只对后一半位置记录 b i t s e t bitset bitset,然后再做一次 d p dp dp,且记录前一半的 b i t s e t bitset bitset 转移
这样可以把空间优化掉一半,就可以过了
时间复杂度 O ( n m ) O(nm) O(nm)

#include 
using namespace std;
const int N=21000;
int n,m,p,f[2][N],x[N],y[N];
bitset<N/2> bs[N];
vector<int> ans;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void chkmax(int &x,int y){ x=max(x,y);}
int main(){
	n=read(),m=read(),p=read();
    for(int i=0;i<n;i++) x[i]=read();
    for(int i=0;i<m;i++) y[i]=read();
    for(int i=0;i<n;i++) bs[i].reset();
    f[0][0]=(x[0]+y[0])%p;
    for(int i=1;i<m;i++) f[0][i]=f[0][i-1]+(x[0]+y[i])%p;
	for(int i=1;i<n;i++){
        memset(f[i&1],0,sizeof(f[i&1]));
        f[i&1][0]=f[~i&1][0]+(x[i]+y[0])%p;bs[i][0]=1;
		for(int j=1;j<m;j++){
			chkmax(f[i&1][j],f[~i&1][j]+(x[i]+y[j])%p);
			chkmax(f[i&1][j],f[i&1][j-1]+(x[i]+y[j])%p);
            if(j>=m/2){
                if(f[~i&1][j]+(x[i]+y[j])%p>f[i&1][j-1]+(x[i]+y[j])%p) bs[i][j-m/2]=1;
                else bs[i][j-m/2]=0;
            }
		}
    }
	printf("%d\n",f[~n&1][m-1]);
    int pos=m-1,cur=n-1;
    for(;cur||pos;){
        if(bs[cur][pos-m/2]) cur--,ans.push_back(1);
        else pos--,ans.push_back(0);
        if(pos<m/2) break;
    }
    for(int i=0;i<n;i++) bs[i].reset();
    memset(f[0],0,sizeof(f[0]));
    f[0][0]=(x[0]+y[0])%p;
    for(int i=1;i<m;i++) f[0][i]=f[0][i-1]+(x[0]+y[i])%p;
    for(int i=1;i<=cur;i++){
        memset(f[i&1],0,sizeof(f[i&1]));
        f[i&1][0]=f[~i&1][0]+(x[i]+y[0])%p;bs[i][0]=1;
		for(int j=1;j<m/2;j++){
			chkmax(f[i&1][j],f[~i&1][j]+(x[i]+y[j])%p);
			chkmax(f[i&1][j],f[i&1][j-1]+(x[i]+y[j])%p);
            if(f[~i&1][j]+(x[i]+y[j])%p>f[i&1][j-1]+(x[i]+y[j])%p) bs[i][j]=1;
            else bs[i][j]=0;
		}
    }
    for(;cur||pos;){
        if(bs[cur][pos]) cur--,ans.push_back(1);
        else pos--,ans.push_back(0);
    }
    for(int i=ans.size()-1;i>=0;i--)
        if(ans[i]) putchar('C');
        else putchar('S');
	return 0;
}

CF67C

题目链接

点击打开链接

题目解法

如果只有前三个操作就很 simple
考虑最后一个操作交换
每次显然只会交换最近的两组可以交换的
于是时间复杂度 O ( n 2 ) O(n^2) O(n2),感觉挺好理解的

#include 
#define lowbit(x) x&-x
using namespace std;
const int N=4100;
int n,m,ti,td,tr,te;
int dp[N][N];
char s[N],t[N];
int p1[N][26],p2[N][26];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void chkmin(int &x,int y){ x=min(x,y);}
int main(){
	ti=read(),td=read(),tr=read(),te=read();
	scanf("%s",s+1),scanf("%s",t+1);
	n=strlen(s+1),m=strlen(t+1);
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<26;j++) p1[i][j]=p1[i-1][j];
        p1[i][s[i]-'a']=i;
    }
    for(int i=1;i<=m;i++){
        for(int j=0;j<26;j++) p2[i][j]=p2[i-1][j];
        p2[i][t[i]-'a']=i;
    }
	for(int i=0;i<=n;i++){
		for(int j=0;j<=m;j++){
			if(!i&&!j) continue;
			if(i>0&&j>0&&s[i]==t[j]) chkmin(dp[i][j],dp[i-1][j-1]);
            if(i>1&&j>1){
                if(p2[j-1][s[i]-'a']&&p1[i-1][t[j]-'a']){
                    int k=p2[j-1][s[i]-'a'],l=p1[i-1][t[j]-'a'];
                    chkmin(dp[i][j],dp[l-1][k-1]+te+ti*(j-1-k)+td*(i-1-l));
                }
            }
			//insert
			if(j>0) chkmin(dp[i][j],dp[i][j-1]+ti);
			//delete
			if(i>0) chkmin(dp[i][j],dp[i-1][j]+td);
			//update
			if(i>0&&j>0) chkmin(dp[i][j],dp[i-1][j-1]+tr);
		}
    }
	printf("%d\n",dp[n][m]);
	fprintf(stderr, "%d ms\n", int(1e3 * clock() / CLOCKS_PER_SEC));
	return 0;
}

CF48G

题目链接

点击打开链接

题目解法

巨大恶心题!!!
简要题意:给定一个 n n n 点无向基环树,求每个点到其余点最短路之和
这道题一眼就会了,但代码是真的麻烦
基环树无非破环为链,然后先考虑子树内的距离和,然后再考虑环上其他子树的贡献
这里需要讨论的地方就是两点只会走 ≤ 1 2 \le \frac{1}{2} 21环长的链,这个有一些细节需要考虑
一个比较轻松的写法是倍长环
时间复杂度 O ( n ) O(n) O(n)

#include 
#define int long long
using namespace std;
const int N=400100;
int n,ans[N];
bool flg;
int e[N<<1],ne[N<<1],w[N<<1],h[N],idx;
int stk[N],top;
int cir[N],cnt,cw[N];
bool oncir[N],vis[N];
int totd[N],siz[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
bool find_cir(int u,int from){
	stk[++top]=u,vis[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==from) continue;
        if(flg) return true;
		if(vis[v]){//find a circle
			while(top&&stk[top]!=v) oncir[stk[top]]=1,cir[++cnt]=stk[top--];
            oncir[v]=1,cir[++cnt]=v;
            flg=1;
			return true;
		}
		if(find_cir(v,u)) return true;
	}
	vis[u]=0,top--;
	return false;
}
void dfs1(int u,int fa){
	totd[u]=0,siz[u]=1;
	for(int i=h[u];~i;i=ne[i])
		if(e[i]!=fa&&!oncir[e[i]]){
            dfs1(e[i],u);
			totd[u]+=totd[e[i]]+siz[e[i]]*w[i];
			siz[u]+=siz[e[i]];
		}
}
void dfs2(int u,int fa,int updis,int rt){
    int tmp=totd[u];
	totd[u]+=updis;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v!=fa&&!oncir[e[i]])
            dfs2(v,u,updis+tmp-totd[v]-siz[v]*w[i]+(siz[rt]-siz[v])*w[i],rt);
	}
}
void dfs3(int u,int fa,int v1,int v2,int curd){
	ans[u]+=v1+v2*curd;
	for(int i=h[u];~i;i=ne[i]) if(e[i]!=fa&&!oncir[e[i]]) dfs3(e[i],u,v1,v2,curd+w[i]);
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
signed main(){
	n=read();
    memset(h,-1,sizeof(h));
	for(int i=1;i<=n;i++){
		int a=read(),b=read(),c=read();
		add(a,b,c),add(b,a,c);
	}
	for(int i=1;i<=n;i++) if(!vis[i]) find_cir(i,-1);
	for(int i=1;i<=cnt;i++) dfs1(cir[i],-1),dfs2(cir[i],-1,0,cir[i]);
	for(int i=1;i<=n;i++) ans[i]=totd[i];
	for(int i=1;i<=cnt;i++) cir[i+cnt]=cir[i];
    for(int i=2;i<=cnt+1;i++){
        int ww=0;
        for(int j=h[cir[i]];~j;j=ne[j]) if(e[j]==cir[i-1]) ww=w[j];
        cw[i]=cw[i+cnt]=ww;
    }
    for(int i=1;i<=cnt<<1;i++) cw[i]+=cw[i-1];
    int totlenth=cw[cnt+1];
	int tot=0,totsiz=0;
	for(int i=1,j=1;i<=cnt<<1;i++){
        while((cw[i]-cw[j])*2>totlenth){
            tot-=totd[cir[j]]-siz[cir[j]]*cw[j],totsiz-=siz[cir[j]];
            j++;
        }
        if(i>cnt){
			int res=totsiz*cw[i]+tot;
			dfs3(cir[i],-1,res,totsiz,0);
		}
		tot+=totd[cir[i]]-siz[cir[i]]*cw[i],totsiz+=siz[cir[i]];
	}
    tot=0,totsiz=0;
	for(int i=cnt<<1,j=cnt<<1;i;i--){
        while((cw[j]-cw[i])*2>=totlenth){
            tot-=totd[cir[j]]+siz[cir[j]]*cw[j],totsiz-=siz[cir[j]];
            j--;
        }
		if(i<=cnt){
			int res=-totsiz*cw[i]+tot;
			dfs3(cir[i],-1,res,totsiz,0);
		}
		tot+=totd[cir[i]]+siz[cir[i]]*cw[i],totsiz+=siz[cir[i]];
	}
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);puts("");
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF11E

看到百分比,需要想到 01 01 01 分数规划,这是一个很重要的思路
然后直接套路二分 +    d p + \;dp +dp 既可
即每正确一步就会 + 1 +1 +1,每走一步就会 − m i d -mid mid
不难直接 d p dp dp
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include 
using namespace std;
const int N=2000100;
const double eps=1e-9,inf=1000000000;
char str[N],t[N];
int n;
double dp[N][2];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
inline double max(double x,double y){
	if(x>y)return x;
	return y;
}
bool check(double mid){//每选一个数就会-mid
	dp[0][1]=0,dp[0][0]=-mid;
	for(int i=1;i<=n;i++){
		dp[i][0]=max(dp[i-1][0]+(str[i]=='R')-2.0*mid,dp[i-1][1]+(str[i]=='L')-mid);
		dp[i][1]=max(dp[i-1][1]+(str[i]=='L')-2.0*mid,dp[i-1][0]+(str[i]=='R')-mid);
	}
	return dp[n][1]>=0;
}
int main(){
	scanf("%s",str+1);
	n=strlen(str+1);
    int len=0;
    if(str[1]==str[n]&&str[1]=='R') t[++len]='X';
    t[++len]=str[1];
    for(int i=2;i<=n;i++){
        if(str[i]==str[i-1]&&str[i]!='X') t[++len]='X';
        t[++len]=str[i];
    }
    if(str[1]==str[n]&&str[1]=='L') t[++len]='X';
    n=len;
    for(int i=1;i<=len;i++) str[i]=t[i];
	double l=0,r=100+eps;
	while(l<r-eps){
		double mid=(l+r)/2;
		check(mid/100)?l=mid:r=mid;
	}
	printf("%.6lf\n",(int)(r*1000000)/1000000.0);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF321D

题目链接

点击打开链接

题目解法

有些妙的题!
考虑一个结论:一个网格可以通过任意次操作得到,当且仅当 a i , x ⊕ a i , m ⊕ a i , x + m = 0    ( 1 ≤ x < m ) a_{i,x}\oplus a_{i,m}\oplus a_{i,x+m}=0\;(1\le x< m) ai,xai,mai,x+m=0(1x<m),对于 a y , j a_{y,j} ay,j 也同理
这个结论感性理解是好理解的,必要性是如果翻了 a i , x a_{i,x} ai,x a i , x + m a_{i,x+m} ai,x+m 那么一定会翻到 a i , m a_{i,m} ai,m,那么异或和始终唯一,充分性我只会感性理解,可能可以通过构造的方式得出
这样就只需要枚举 m ∗ m m*m mm 的网格状态,就可以确定其他网格的状态
我们发现第 m m m 行是一个关键行,因为上下异或都需要用到它,所以考虑状压这一行前 m m m 列的状态
然后一行一行往下计算,因为这时在每个区域(可以以 ( m , m ) (m,m) (m,m) 把网格分成 4 个区域)的状态互不影响,所以可以通过一些判断计算出来,这个不难计算
时间复杂度 O ( 2 m m 2 ) O(2^mm^2) O(2mm2)
感觉一开始的小结论有些妙,其他比较套路

#include 
using namespace std;
const int N=40;
int a[N][N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int calc(int x,int y){
	if(y) return -x;
	return x;
}
void chkmax(int &x,int y){ x=max(x,y);}
int main(){
	int n=read(),m=(n+1)/2;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=read();
    int ans=-1e9;
	for(int S=0;S<1<<m;S++){//枚举第m行前m个的状态
		int Sm=S>>(m-1)&1;
		int res=calc(a[m][m],Sm);
		for(int i=0;i<m-1;i++){
			int p=S>>i&1,q=Sm^p;
			res+=calc(a[m][i+1],p)+calc(a[m][i+m+1],q);
		}
		for(int j=1;j<m;j++){//枚举第j行
			int MX=-1e9;
			for(int Sj=0;Sj<2;Sj++){
				int rs=calc(a[j][m],Sj)+calc(a[j+m][m],Sj^Sm);
				for(int k=1;k<m;k++){
					int mx=-1e9;
					for(int A=0;A<2;A++){//(x,y)
						int B=A^Sj;//(x,y+m)
						int C=A^(S>>(k-1)&1);//(x+m,y)
						int D=C^(Sm^Sj);//(x+m,y+m)
						chkmax(mx,calc(a[j][k],A)+calc(a[j][k+m],B)+calc(a[j+m][k],C)+calc(a[j+m][k+m],D));
					}
					rs+=mx;
				}
				chkmax(MX,rs);
			}
			res+=MX;
		}
		ans=max(ans,res);
	}
	printf("%d\n",ans);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF251E

题目链接

点击打开链接

题目解法

很好的题!!!
参考了 Luogu 的题解,感觉很妙
显然存在度数大于 4 4 4 的点答案就是 0 0 0,直接特判掉
我们任意找一个三度点作为根,这一步主要是为了把问题分成左右两边去分治
这里需要特判掉一条链的情况
考虑令 d p u dp_u dpu 表示用 i i i 的子树填满一个 2 × s i z u 2 2\times \frac{siz_u}{2} 2×2sizu 的方格的方案数
我们找到在 u u u 的子树内离 u u u 最近的二度点
发现可能的情况有:
dp训练题解_第1张图片
其中 y y y 可能是在上面也可能是在下面,这都是小事
所以我们可以枚举 v v v 的那个儿子处在下面,那个继续往左边延伸
然后就是复杂的分类讨论(令 v v v 的两个儿子分别为 w 1 , w 2 w1,w2 w1,w2,不妨令 v v v 在下面,且 w 1 w1 w1 为第一步向上延伸的, w 2 w2 w2 为第一步向左延伸的)
这里需要引入一个新的 d p dp dp 数组 g x , y g_{x,y} gx,y 表示把 x x x 的子树和 y y y 的子树分别往同一个方向延伸的方案数

  1. w 2 w2 w2 w 1 w1 w1 的儿子一起往右延伸,即为 g w 2 , s o n w 1 g_{w2,son_{w1}} gw2,sonw1,条件是 w 1 w1 w1 只有一个儿子
  2. w 1 w1 w1 的子树是链,且可以往右边延伸,那么 w 2 w2 w2 就独占 2 × s i z w 2 2 2\times \frac{siz_{w2}}{2} 2×2sizw2 的网格
  3. w 1 w1 w1 为有 2 个儿子,那么需要考虑哪一个儿子与 w 1 w1 w1 一起往左延伸,哪一个儿子往右延伸

有亿些情况需要特判,因为 d p dp dp g g g 需要互相调用,所以这里使用记忆化搜索,时间复杂度 O ( n ) O(n) O(n)

#include 
using namespace std;
const int N=200100,P=1e9+7;
int n,dp[N];
int deg[N],nxt2[N],dwlen[N],siz[N];
int e[N<<1],ne[N<<1],h[N],idx;
vector<int> G[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
void dfs(int u,int fa){
    siz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==fa) continue;
		G[u].push_back(v),dfs(v,u);
		siz[u]+=siz[v];
		nxt2[u]=nxt2[v],dwlen[u]=dwlen[v]+1;
	}
	if(G[u].size()==2) nxt2[u]=u,dwlen[u]=0;
}
inline void inc(int &x,int y){
	x+=y;
	if(x>=P) x-=P;
}
int calc1(int u);
int calc2(int u1,int u2){
	if((siz[u1]+siz[u2])&1) return 0;
	if(!u1||!u2) return calc1(u1|u2);
	if(G[u1].size()>1||G[u2].size()>1) return 0;
    if(!G[u1].size()&&!G[u2].size()) return 1;
	if(!G[u1].size()) return calc1(G[u2][0]);
	if(!G[u2].size()) return calc1(G[u1][0]);
	return calc2(G[u1][0],G[u2][0]);
}
int calc1(int u){
	if(!u) return 1;
	if(siz[u]&1) return 0;
	if(dp[u]!=-1) return dp[u];
	if(!nxt2[u]) return siz[u]/2;
	int v1=G[nxt2[u]][0],v2=G[nxt2[u]][1],res=0;
	for(int k=0;k<2;k++){
		if(G[v1].size()==1) inc(res,calc2(v2,G[v1][0]));
        if(!nxt2[v1]&&dwlen[v1]<=dwlen[u]) inc(res,calc1(v2)*((dwlen[v1]!=dwlen[u])+1)%P);
		if(G[v1].size()==2){
			int w1=G[v1][0],w2=G[v1][1];
			if(!nxt2[w1]&&dwlen[w1]<dwlen[u]) inc(res,calc2(w2,v2));
            if(!nxt2[w2]&&dwlen[w2]<dwlen[u]) inc(res,calc2(w1,v2));
		}
		swap(v1,v2);
	}
	return dp[u]=res;
}
int main(){
	n=read();n<<=1;
    memset(h,-1,sizeof(h));
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y),add(y,x),deg[x]++,deg[y]++;
	}
	for(int i=1;i<=n;i++) if(deg[i]>3){ puts("0");exit(0);}
	int mxdeg=0;
	for(int i=1;i<=n;i++) mxdeg=max(mxdeg,deg[i]);
    if(mxdeg==1){ puts("2");exit(0);}
	if(mxdeg==2){ printf("%d",1ll*2*(n+1ll*(n/2-1)*(n/2-2)%P)%P);exit(0);}
	int rt,ans=0;
	for(int i=1;i<=n;i++) if(deg[i]==3) rt=i;
	memset(dp,-1,sizeof(dp));
	dfs(rt,0);
    for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
	do{
		int u=G[rt][0],v=G[rt][1],w=G[rt][2];
        if(G[u].size()>2) continue;
		if(!G[u].size()) inc(ans,1ll*calc1(v)*calc1(w)%P);
		if(G[u].size()==1) inc(ans,1ll*calc1(v)*calc2(w,G[u][0])%P),inc(ans,1ll*calc1(w)*calc2(v,G[u][0])%P);
		if(G[u].size()==2) inc(ans,1ll*calc2(v,G[u][0])*calc2(w,G[u][1])%P),inc(ans,1ll*calc2(v,G[u][1])*calc2(w,G[u][0])%P);
	}while(next_permutation(G[rt].begin(),G[rt].end()));
	printf("%d\n",2*ans%P);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

你可能感兴趣的:(其他,算法)