(FJWC2020day3) DP课件题解

校内教练分配了DP专题,感觉这里的题不错。
当时没有一题是在讲评之前想出来的(没救了)。
后来看了下其实没有很难(指大部分题),当时可能太困了没认真想。
总之还是菜吧。

T1

AGC033D.Complexity
如果直接DP设 f [ i ] [ j ] [ k ] [ l ] f[i][j][k][l] f[i][j][k][l]记录矩形,瓶颈在于状态数上。
考虑减少状态数,其实也是一个比较套路的方法,发现答是 l o g log log级别的,于是把其中一维状态改为答案,DP值改为那维状态,转移时考虑横着还是竖着切,一边直接用上一个转,一边利用单调性转即可,效率 O ( n 3 l o g n ) O(n^{3}logn) O(n3logn)
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,f[2][N][N][N],a[N][N];
bool check(int j,int k,int l,int r){
	int x=(k-j+1)*(r-l+1),y=a[k][r]-a[j-1][r]-a[k][l-1]+a[j-1][l-1];
	return (!y || x==y);
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
		char c; scanf(" %c",&c);
		a[i][j]=(c=='#')+a[i-1][j]+a[i][j-1]-a[i-1][j-1];
	}
	for(int x=0;x<2;x++) for(int j=1;j<=n+1;j++)
		for(int l=1;l<=m;l++) f[x][j][j-1][l]=N;
	for(int j=1;j<=n;j++) for(int k=j;k<=n;k++){
		for(int l=m;l;l--){
			if(l==m) f[0][j][k][l]=m;
			else f[0][j][k][l]=f[0][j][k][l+1];
			while(!check(j,k,l,f[0][j][k][l])) f[0][j][k][l]--;
			//cout<<j<<" "<<k<<" "<<l<<" "<<f[0][j][k][l]<<endl;
		}
	}
	//return 0;
	for(int i=1;;i++){
		int x=(i&1);
		if(f[!x][1][n][1]==m){
			cout<<i-1<<endl; return 0;
		}
		for(int j=1;j<=n;j++) for(int k=j;k<=n;k++)
			for(int l=1;l<=m;l++) f[x][j][k][l]=f[!x][j][k][f[!x][j][k][l]+1];
		for(int j=1;j<=n;j++) for(int l=1;l<=m;l++){
			for(int k=j,p=j;k<=n;k++){
				while(p<k && min(f[!x][j][p][l],f[!x][p+1][k][l])<=min(f[!x][j][p+1][l],f[!x][p+2][k][l])) p++;
				f[x][j][k][l]=max(f[x][j][k][l],min(f[!x][j][p][l],f[!x][p+1][k][l]));
			}
		}
	}
	return 0;
}

T2

CF585F.Digits of Number Pi
如果子串必须是 S S S的前缀,那么是一个经典的单个字符串匹配问题,用KMP+数位DP记录目前匹配到的位置即可。
而任意子串就相当于 S S S的所有后缀的前缀,是多个字符串匹配问题,又注意到有用的只有每个后缀的前 d / 2 d/2 d/2个字符,对它们建立 A C AC AC自动机即可,效率 O ( 10 d 2 s ) O(10d^{2}s) O(10d2s)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P=1e9+7;
const int N=55,M=50005,K=10;
char ch[M];
void read(int* a,int& n){
	scanf("%s",ch+1); n=strlen(ch+1);
	for(int i=1;i<=n;i++) a[i]=ch[i]-'0';
}
int n,m,a[M],to[M][K],fa[M],ct=1;
bool is[M];
void add(int l,int r){
	int u=1;
	for(int i=l;i<=r;i++){
		int x=a[i];
		if(!to[u][x]) to[u][x]=++ct;
		u=to[u][x];
	}
	is[u]=1;
}
void bfs(){
	queue<int>q; fa[1]=1;
	for(int i=0;i<K;i++){
		int& u=to[1][i];
		if(u) fa[u]=1,q.push(u);
		else u=1; 
	}
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int i=0;i<K;i++){
			int& v=to[u][i];
			if(v) fa[v]=to[fa[u]][i],q.push(v);
			else v=to[fa[u]][i];
		}
	}
}
ll f[N][M];
ll cnt(int* s){
	memset(f,0,sizeof(f));
	int u=1;
	for(int i=0;i<m;i++){
		for(int k=0;k<s[i+1];k++){
			int v=to[u][k]; if(is[v]) v=0;
			(f[i+1][v]+=1)%=P;
		}
		for(int j=0;j<=ct;j++){
			for(int k=0;k<K;k++) if(f[i][j]){
				int v=to[j][k]; if(is[v]) v=0;
				(f[i+1][v]+=f[i][j])%=P;
			}
		}
		u=to[u][s[i+1]]; if(is[u]) u=0;
	}
	//cout<<(f[m][0]+(u==0))%P<<endl;
	return (f[m][0]+(u==0))%P;
}
int x[N],y[N];
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	read(a,n),read(x,m),read(y,m);
	for(int i=m;i;i--){
		if(!x[i]) x[i]=9;
		else{
			x[i]--; break;
		}
	}
	for(int i=1;i+m/2-1<=n;i++) add(i,i+m/2-1);
	bfs(); //return 0;
	/*for(int i=1;i<=ct;i++){
		cout<<"u="<<i<<" fa="<<fa[i]<<endl;
		for(int j=0;j<K;j++) cout<<to[i][j]<<" ";
		puts("");
	}*/
	cout<<(cnt(y)+P-cnt(x))%P<<endl;
	return 0;
}

T3

AGC030D.Inversion Sum
直接算每对 i , j i,j i,j的贡献,发现每次操作只会对 4 n 4n 4n对的值有影响,其他都是 ∗ 2 *2 2,于是把方案数改为期望即可,效率 O ( n 2 + n q ) O(n^{2}+nq) O(n2+nq)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P=1e9+7,V=(P+1)>>1;
const int N=3005;
int n,q,a[N]; ll f[N][N],g[N][N],ans,s=1;
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>q;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
		f[i][j]=(a[i]<a[j]); g[i][j]=(a[i]>a[j]);
	}
	while(q--){
		int x,y; scanf("%d%d",&x,&y);
		f[x][y]=f[y][x]=(f[x][y]+f[y][x])*V%P;
		g[x][y]=g[y][x]=(g[x][y]+g[y][x])*V%P;
		for(int i=1;i<=n;i++) if(i!=x && i!=y){
			f[x][i]=f[y][i]=(f[x][i]+f[y][i])*V%P;
			g[x][i]=g[y][i]=(g[x][i]+g[y][i])*V%P;
			f[i][x]=f[i][y]=(f[i][x]+f[i][y])*V%P;
			g[i][x]=g[i][y]=(g[i][x]+g[i][y])*V%P;
		}
		(s*=2)%=P;
	}
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) (ans+=g[i][j])%=P;
	cout<<ans*s%P<<endl;
	return 0;
}

T4

卡常垃圾!!!
CF568E. Longest Increasing Subsequence
如果没有空位,那么是一个经典的求LIS的问题,直接用树状数组维护dp[i]表示以数字i为结尾的LIS即可。
注意到比较不好做的地方在于m个数每个只能用一次,如果多记一维目前用到哪效率会炸。但既然要求严格上升,那么重复用一个数肯定没有用,所以完全可以忽略这个限制。于是遇到一个空位,直接用 O ( m ) O(m) O(m)对目前所有dp值进行更新即可(树状数组因为是前缀max可做到 O ( m ) O(m) O(m)重构)。
记录方案时,如果要记录每个状态的转移点空间会炸,考虑先忽略空位,然后在LIS的相邻两个数之间,尽量按顺序填入这些空位,其它空位乱填即可,效率 O ( m k + n l o g n ) O(mk+nlogn) O(mk+nlogn)
代码:(毒瘤题卡常卡到怀疑人生)

#include<bits/stdc++.h>
using namespace std;
int R(){
	int s=0; bool p=0; char c=getchar();
	while(!isdigit(c)){
		if(c=='-') p=1; c=getchar();
	}
	while(isdigit(c)) s=(s<<3)+(s<<1)+(c-48),c=getchar();
	if(p) s=-s; return s;
}
const int N=2e5+5;
int n,m,a[N],b[N],c[N],q[N],t;
#define st first
#define nd second
#define mp make_pair
pair<int,int> dp[N],mx[N],pt[N],tp[N];
void upd(pair<int,int> u,int x){
	for(;x<=t;x+=(x&(-x))) if(u.st>mx[x].st) mx[x]=u;
}
pair<int,int> qry(int x){
	pair<int,int> u=mp(0,0);
	for(;x;x-=(x&(-x))) if(mx[x].st>u.st) u=mx[x];
	return u;
}
int ps[N],tt;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		a[i]=R();
		if(~a[i]) q[++t]=a[i];
	}
	cin>>m;
	for(int i=1;i<=m;i++) b[i]=R(),q[++t]=b[i];
	sort(q+1,q+t+1); t=unique(q+1,q+t+1)-q-1;
	for(int i=1;i<=m;i++) b[i]=lower_bound(q+1,q+t+1,b[i])-q,c[b[i]]++;
	sort(b+1,b+m+1); m=unique(b+1,b+m+1)-b-1;
	for(int i=1;i<=n;i++){
		if(~a[i]){
			a[i]=lower_bound(q+1,q+t+1,a[i])-q;
			pair<int,int> u=qry(a[i]-1); pt[i]=u;
			u.st++; u.nd=i;
			if(u.st>dp[a[i]].st) dp[a[i]]=u,upd(u,a[i]);
		}
		else{
			for(register int i=1;i<=t;i++){
				tp[i]=dp[i];
				if(tp[i-1].st>tp[i].st) tp[i]=tp[i-1];
			}
			for(register int i=1;i<=m;i++){
				pair<int,int> v=tp[b[i]-1]; v.st++;
				if(v.st>dp[b[i]].st) dp[b[i]]=v;
			}
			for(register int i=1;i<=t;i++){
				mx[i]=dp[i];
				if(mx[i-1].st>mx[i].st) mx[i]=mx[i-1];
			}
		}
	}
	pair<int,int> u=qry(t);
	while(u.st){
		ps[++tt]=u.nd;
		u=pt[u.nd];
	}
	reverse(ps+1,ps+tt+1); ps[tt+1]=n+1; a[n+1]=t+1;
	for(int i=0,j=1;i<=tt;i++){
		for(int k=ps[i]+1;k<ps[i+1];k++) if(a[k]==-1){
			while(j<=a[ps[i]]) j++;
			while(!c[j] && j+1<a[ps[i+1]]) j++;
			if(c[j] && j<a[ps[i+1]]){
				a[k]=j; c[j]--; j++;
			}
		}
	}
	for(int i=1,j=1;i<=n;i++) if(a[i]==-1){
		while(!c[j]) j++; a[i]=j; c[j]--;
	}
	for(int i=1;i<=n;i++) printf("%d ",q[a[i]]);
	return 0;
}

CF体验极差,此题卡常极其恶心,我卡了一天自闭了。
改了个地方以为常数能除2,调了几百年发现假了,心态炸裂。
它到底做错了什么???难道有任何地方有可能优化吗??????
关键是,为什么,没有人,和我一样,TLE在12???
垃圾题真的毁我青春,我不管了。
我对CF有阴影了,Atcoder多好啊(目前没wa过 虽然只写了4题 )。

T5

CF724E. Goods transportation
首先容易建出最大流的图,但边数是 O ( n 2 ) O(n^{2}) O(n2)级别的,且每对 i < j i<j i<j之间都有连边,难以用数据结构优化。考虑转化为等价的最小割问题,发现割边只有每个点与 s , t s,t s,t点相连的边以及 s s s集合到 t t t集合的边,可直接DP,设 f [ i ] [ j ] f[i][j] f[i][j]为前 i i i个点有 j j j个属于 s s s集合,转移显然。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10005; const ll F=1e18;
int n; ll s[N],t[N],c,f[2][N],ans=F;
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>c;
	for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
	for(int i=0;i<2;i++) for(int j=0;j<=n;j++) f[i][j]=F;
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		int x=(i&1);
		for(int j=0;j<=i;j++){
			f[x][j]=f[!x][j]+s[i]+c*j;
			if(j) f[x][j]=min(f[x][j],f[!x][j-1]+t[i]);
			if(i==n) ans=min(ans,f[x][j]);
		}
	}
	cout<<ans<<endl;
	return 0;
}

T6

CF536D. Tavas in Kansas
对于博弈问题考虑倒着DP,首先把 s , t s,t s,t到每个点的最短路求出来离散化一下,设 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1] s s s取完距离它前 i i i近的点, t t t取完距离它前 j j j近的点后, s / t s/t s/t先手,之后对于 s / t s/t s/t的最优解,转移时枚举 s / t s/t s/t取到哪,显然可以前缀 m i n min min优化,效率做到 O ( n 2 ) O(n^{2}) O(n2)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2005,M=2e5+5;
int n,m,S,T,a[N],hd[N],to[M],nx[M],wl[M],tt;
void add(int u,int v,int w){
	nx[++tt]=hd[u]; to[hd[u]=tt]=v; wl[tt]=w;
}
ll d[2][N],F=1e15;
struct node{
	int x; ll d;
	bool operator < (const node& r) const{
		return d>r.d;
	};
};
priority_queue<node>q;
void bfs(int s,bool p){
	for(int i=1;i<=n;i++) d[p][i]=F;
	d[p][s]=0; q.push((node){s,0});
	while(!q.empty()){
		node u=q.top(); q.pop();
		if(d[p][u.x]!=u.d) continue;
		for(int e=hd[u.x];e;e=nx[e]){
			int v=to[e]; if(u.d+wl[e]>=d[p][v]) continue;
			d[p][v]=u.d+wl[e]; q.push((node){v,d[p][v]});
		}
	}
	ll q[N]; memcpy(q,d[p],sizeof(d[p])); sort(q+1,q+n+1);
	//cout<<"p="<<p<<endl;
	for(int i=1;i<=n;i++) d[p][i]=lower_bound(q+1,q+n+1,d[p][i])-q;//cout<<d[p][i]<<" "; puts("");
}
int ms[N][N],mt[N][N];
ll ss[N][N],st[N][N],f[N][N],g[N][N],mf[N][N],mg[N][N];
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m>>S>>T;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int u,v,w,i=1;i<=m;i++)
		scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
	bfs(S,0); bfs(T,1);
	for(int i=0;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(d[0][j]>i) st[i][d[1][j]]+=a[j],mt[i][d[1][j]-1]=d[1][j];
			if(d[1][j]>i) ss[d[0][j]][i]+=a[j],ms[d[0][j]-1][i]=d[0][j];
		}
		for(int j=1;j<=n;j++) st[i][j]+=st[i][j-1],ss[j][i]+=ss[j-1][i];
		for(int j=n;~j;j--){
			if(!mt[i][j]) mt[i][j]=mt[i][j+1];
			if(!ms[j][i]) ms[j][i]=ms[j+1][i];
		}
		/*cout<<"i="<<i<<endl;
		for(int j=1;j<=n;j++) cout<<mt[i][j]<<" "; cout<<endl;
		for(int j=1;j<=n;j++) cout<<st[i][j]<<" "; cout<<endl;
		for(int j=1;j<=n;j++) cout<<ms[j][i]<<" "; cout<<endl;
		for(int j=1;j<=n;j++) cout<<ss[j][i]<<" "; cout<<endl;*/
	}
	//cout<<ms[4][0]<<endl;
	for(int i=n;~i;i--) for(int j=n;~j;j--){
		if(ms[i][j]) f[i][j]=-ss[i][j]-mg[ms[i][j]][j];
		mf[i][j]=f[i][j]-st[i][j];
		if(j<n) mf[i][j]=min(mf[i][j],mf[i][j+1]);
		if(mt[i][j]) g[i][j]=-st[i][j]-mf[i][mt[i][j]];
		mg[i][j]=g[i][j]-ss[i][j];
		if(i<n) mg[i][j]=min(mg[i][j],mg[i+1][j]);
		//printf("i=%d j=%d f=%lld g=%lld\n",i,j,f[i][j],g[i][j]);
	}
	//if(n==468) cout<<f[0][0]<<endl;
	//cout<<f[0][0]<<endl;
	if(f[0][0]<0) puts("Cry");
	else if(!f[0][0]) puts("Flowers");
	else puts("Break a heart");
	return 0;
}
/*
4 3
1 4
-1 -1 -1 -1
1 2 1
2 3 1
3 4 1
*/

突然发现,很多DP题都是一开始有个大概思路,然后懒得列式子在那里干瞪眼,越想越不可做,后来列出式子一切迎刃而解。

T7

AGC022E - Median Replace
先考虑对于一个给定的字符串如何判断是否合法。从前往后考虑一个个字符加进来的情况,注意到一些奇妙的性质:1.若当前开头有2个1,则必合法;2.对于相连的01或10,可选择忽视(和任何一个数结合结果都是那个数)。
考虑把性质1作为目标,则01直接忽视,10先保留,这样当前序列一定是一些1后接着一些0,只需判断最后的序列是否不少于2个1,或只有1个1而没有0。
对于问号,考虑DP记录状态,1的状态只有三种(大于等于2都一样),连续的3个0显然缩为一个更优,于是0也只有三种状态,效率 O ( n ) O(n) O(n)
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,P=1e9+7;
char a[N]; int n,f[N][3][3];
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	scanf("%s",a+1); n=strlen(a+1); f[0][0][0]=1;
	for(int i=0;i<n;i++) 
		for(int j=0;j<3;j++) for(int k=0;k<3;k++) if(f[i][j][k]){
			int v=f[i][j][k];
			for(int x=0;x<2;x++) if(x==a[i+1]-'0' || a[i+1]=='?'){
				if(j){
					int y;
					if(!x){
						y=j+1; if(y==3) y=1;
					}
					else y=j-1;
					(f[i+1][y][k]+=v)%=P;
				}
				else{
					if(k==2) (f[i+1][0][2]+=v)%=P;
					else{
						if(!x) (f[i+1][1][k]+=v)%=P;
						else (f[i+1][0][k+1]+=v)%=P;
					}
				}
			}
		}
	int ans=f[n][0][1];
	for(int i=0;i<3;i++) (ans+=f[n][i][2])%=P;
	cout<<ans<<endl;
	return 0;
}

T8

CF708E. Student’s Camp
容易想到暴力: f [ i ] [ l ] [ r ] f[i][l][r] f[i][l][r]为前i行四连通,最后一行剩下的区间为 [ l , r ] [l,r] [l,r]的概率,前缀和优化转移可以做到 O ( n 3 ) O(n^{3}) O(n3)
发现瓶颈在于状态数,考虑利用左右独立的性质来优化。i这维无法避免,想要减少状态只能把单个改为前缀和,记 f [ i ] [ l ] f[i][l] f[i][l]为左端点为 l l l的所有DP值, g [ i ] [ r ] g[i][r] g[i][r]为右端点为 r r r的所有DP值, f , g f,g f,g的转移推出式子后可用前缀和优化到 O ( 1 ) O(1) O(1),于是效率优化到 O ( n 2 ) O(n^{2}) O(n2)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P=1e9+7;
ll fpw(ll a,ll x){
	ll s=1;
	for(;x;x>>=1,(a*=a)%=P) if(x&1) (s*=a)%=P;
	return s;
}
const int N=1505;
int n,m,k; ll x,y;
ll p[N],sp[N],f[N],g[N],cf[N],sf[N],cg[N],sg[N];
void sum(){
	for(int i=1;i<=m;i++){
		cf[i]=(cf[i-1]+f[i])%P,cg[i]=(cg[i-1]+g[i])%P;
		sg[i]=(sg[i-1]+cg[i]*p[i])%P;
	}
	for(int i=m;~i;i--) sf[i]=(sf[i+1]+cf[i]*p[m-i])%P;
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m>>x>>y>>k;
	(x*=fpw(y,P-2))%=P;
	p[0]=1; for(int i=1;i<=k;i++) (p[0]*=(P+1-x))%=P; sp[0]=p[0];
	for(int i=1;i<=m;i++){
		p[i]=p[i-1]*x%P*fpw(P+1-x,P-2)%P*fpw(i,P-2)%P*(k-i+1)%P;
		if(x==1 && i==k) p[i]=1;
		sp[i]=(sp[i-1]+p[i])%P; //cout<<p[i]<<" ";
	}
	f[1]=g[m]=1; sum();
	for(int i=1;i<=n;i++){ //cout<<"i="<<i<<endl;
		for(int l=1;l<=m;l++){
			f[l]=p[l-1]*(sf[l]-cg[l-1]*sp[m-l]%P+P)%P;
			//cout<<f[l]<<" ";
		} //puts("");
		for(int r=1;r<=m;r++){
			g[r]=p[m-r]*(sp[r-1]*cf[r]%P-sg[r-1]+P)%P;
			//cout<<g[r]<<" ";
		} //puts("");
		sum();
	}
	cout<<cf[m]<<endl;
	return 0;
}

T9

ARC093F - Dark Horse
题目相当于有长度为 2 0 , 2 1 , . . . , 2 n − 1 2^{0},2^{1},...,2^{n-1} 20,21,...,2n1 n n n个区间,要在上面填上 2 , . . . , n 2,...,n 2,...,n,使得每个区间的最小值都不为给定的 m m m个数的方案数(最后再乘个 n n n)。
显然最小值不为某些数是不好计算的,考虑容斥。又注意到 m m m很小,枚举哪些人一定是区间最小的,按照编号从大到小考虑那些人。记 f [ i ] [ j ] f[i][j] f[i][j]为考虑了前 i i i个人, n n n个区间分别是否已经有一定是最小值的人,只考虑这些区间的排列情况的方案数,转移时枚举这个人是否一定最小,以及如果是,这个人在哪个区间,乘上组合数即可。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=17,M=(1<<N);
const ll P=1e9+7;
ll fc[M],iv[M];
ll A(int n,int m){
	if(m<0 || m>n) return 0;
	return fc[n]*iv[n-m]%P;
}
ll fpw(ll a,ll x){
	ll s=1;
	for(;x;x>>=1,(a*=a)%=P) if(x&1) (s*=a)%=P;
	return s;
}
int n,m,a[N],p[N];
ll f[N][M],ans;
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	fc[0]=1; for(int i=1;i<M;i++) fc[i]=fc[i-1]*i%P;
	iv[M-1]=fpw(fc[M-1],P-2);
	for(int i=M-2;~i;i--) iv[i]=iv[i+1]*(i+1)%P;
	cin>>n>>m; p[0]=1; for(int i=1;i<=n;i++) p[i]=(p[i-1]<<1);
	for(int i=1;i<=m;i++) scanf("%d",&a[i]);
	reverse(a+1,a+m+1); f[0][0]=1;
	for(int i=0;i<m;i++){
		for(int j=0;j<p[n];j++) if(f[i][j]){
			//printf("i=%d j=%d f=%lld\n",i,j,f[i][j]);
			ll x=f[i][j]; int s=0;
			for(int k=0;k<n;k++) if(j&p[k]) s+=p[k];
			(f[i+1][j]+=x)%=P;
			for(int k=0;k<n;k++) if(!(j&p[k])){
				//cout<<k<<" "<<x*A(p[n]-a[i+1]-s,p[k]-1)%P*p[k]<<endl;
				(f[i+1][j+p[k]]+=x*A(p[n]-a[i+1]-s,p[k]-1)%P*p[k])%=P;
			}
		}
	}
	for(int i=0;i<p[n];i++){
		int s=0,t=1;
		for(int j=0;j<n;j++){
			if(!(i&p[j])) s+=p[j];
			else t=-t;
		}
		(ans+=P+fc[s]*f[m][i]%P*t)%=P;
	}
	cout<<ans*p[n]%P<<endl;
	return 0;
}

T10

ARC096F - Sweet Alchemy
注意到父子关系对个数的限制比较麻烦,直接树形DP记录不下。利用父子关系做一个转化:选一个点时把它的子树也选了,这样问题转化成 n n n个物品,每个物品有价值 v v v和重量 w w w,除了一号物品外每个物品不能选超过 D D D个,求在 X X X的容量内的最大选取价值总和。
这是个经典的多重背包问题,但只有 n n n v v v的范围是 50 50 50,必须利用贪心把物品数减少(物品数 1 e 9 1e9 1e9级别肯定做不了 D P DP DP)。因为 v v v很小,从它出发考虑,使 v v v一定时, w w w尽可能小。考虑一个物品选很多个时是否可被替换成更优:发现物品 i i i选了 v [ j ] v[j] v[j]个时,能被 v [ i ] v[i] v[i]个物品 j j j替换,只要 v [ i ] v[i] v[i]个物品 j j j的重量更小就更优。于是按照 v / w v/w v/w从大到小排序,先考虑不会被替换的部分,只需记 f [ i ] f[i] f[i]为价值为 i i i的最小重量,每个物品只需考虑 n n n个,剩下的再从前往后贪心即可。
DP状态是 O ( n 3 ) O(n^{3}) O(n3)的,转移时对于每个物品,DP值按照 % v [ i ] \% v[i] %v[i]分组,每组间用单调队列维护最优转移点,一次转移可以做到 O ( n ) O(n) O(n),总复杂度 O ( n 4 ) O(n^{4}) O(n4)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=55,M=N*N*N;
int n,m,d,fa[N];
struct node{
	int v; ll w;
}a[N];
bool cmp(node x,node y){
	return y.w*x.v>x.w*y.v;
}
ll f[M],F=1e15;
int ans,p[M]; node q[M];
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m>>d>>a[1].w; a[1].v=1;
	for(int i=2;i<=n;i++) scanf("%lld%d",&a[i].w,&fa[i]),a[i].v=1;
	for(int i=n;i>1;i--) a[fa[i]].w+=a[i].w,a[fa[i]].v+=a[i].v;
	sort(a+1,a+n+1,cmp);
	f[0]=0; for(int i=1;i<M;i++) f[i]=F;
	int l=min(d,n),s=0;
	for(int i=1;i<=n;i++){
		s+=n*a[i].v;
		for(int x=0;x<a[i].v;x++){
			int t=0,ft=0,tl=0;
			for(int j=x;j<=s;j+=a[i].v) p[++t]=j;
			for(int j=t,k=t;j;j--){
				while(ft<tl && q[ft+1].v>j) ft++;
				while(k && k>=j-l){
					node u=(node){k,f[p[k]]-a[i].w*k};
					while(ft<tl && q[tl].w>=u.w) tl--;
					q[++tl]=u;
					k--;
				}
				f[p[j]]=min(f[p[j]],q[ft+1].w+a[i].w*j);
			}
		}
	}
	for(int i=0;i<=s;i++){
		//printf("i=%d x=%lld\n",i,f[i]);
		if(f[i]>m) continue;
		int x=m-f[i],su=i;
		for(int j=1;j<=n;j++){
			int u=(int)(((ll)x)/a[j].w);
			if(a[j].v<n) u=min(u,d-l);
			su+=a[j].v*u; x-=a[j].w*u;
			if(x<a[j].w) break;
		}
		//cout<<"su="<<su<<endl;
		ans=max(ans,su);
	}
	cout<<ans<<endl;
	return 0;
}

T11

CF559E. Gerald and Path
首先把所有出现的点离散化一下。
因为既要考虑区间的前后关系,又要使一个点的左右两个区间不能同时被选,故需要记录状态 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个最右覆盖到 j j j的最长覆盖长度。又因为这样在转移时限制较多比较麻烦,故把 j j j改为考虑到前 j j j个位置(后面不会在覆盖)而不一定覆盖。
设当前点为 ( l , p , r ) (l,p,r) (l,p,r),转移时,考虑这个点向左还是向右:向右就由 f [ i − 1 ] [ p ] f[i-1][p] f[i1][p]转移到 f [ i ] [ r ] f[i][r] f[i][r];向左由于要考虑前面向右的区间可能会伸出去(超过 r r r),就枚举 k k k,使 k + 1 , . . , i − 1 k+1,..,i-1 k+1,..,i1都向右,计算一下最右覆盖到哪里,由 f [ k ] [ l ] f[k][l] f[k][l]转移过来。该过程用前缀max、后缀max优化一下即可做到 O ( n 2 ) O(n^{2}) O(n2)
代码:

#include<bits/stdc++.h>
using namespace std;
const int N=305;
struct node{
	int l,p,r;
}a[N];
bool cmp(node x,node y){
	return x.p<y.p;
}
int n,m,p[N],f[N][N],g[N];
void lb(int& x){
	x=lower_bound(p+1,p+m+1,x)-p;
}
int main()
{
	srand(time(0));
	cin>>n;
	for(int d,i=1;i<=n;i++){
		scanf("%d%d",&a[i].p,&d);
		a[i].l=a[i].p-d; a[i].r=a[i].p+d;
		p[++m]=a[i].l; p[++m]=a[i].p; p[++m]=a[i].r;
	}
	sort(p+1,p+m+1); m=unique(p+1,p+m+1)-p-1;
	for(int i=1;i<=n;i++) lb(a[i].l),lb(a[i].p),lb(a[i].r);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		int l=a[i].l,x=a[i].p,r=a[i].r;
		memcpy(f[i],f[i-1],sizeof(f[i-1]));
		memset(g,0,sizeof(g));
		int mx=x;
		g[mx]=f[i-1][l]+p[mx]-p[l];
		for(int j=i-1;j;j--){
			mx=max(mx,a[j].r);
			g[mx]=max(g[mx],f[j-1][l]+p[mx]-p[l]);
		}
		for(int j=m;j>=l;j--){
			f[i][j]=max(f[i][j],g[j]);
			g[j-1]=max(g[j-1],g[j]+p[j-1]-p[j]);
		}
		for(int j=x;j<=r;j++) f[i][j]=max(f[i][j],f[i-1][x]+p[j]-p[x]);
		for(int j=1;j<=m;j++) f[i][j]=max(f[i][j],f[i][j-1]);
	}
	cout<<f[n][m]<<endl;
	return 0;
}

T12

AGC022F - Checkers
最近做的最神仙的一道DP/计数题。
原本的想法是A关于B对称,就新建一个点C,A向这个点连一条-1的边,B连2的边,这样两种方案不同当且仅当存在一个点到根路径的乘积不同,但这样要同时关心2,-1的个数,难以计数。
发现乘积只跟-1的奇偶性有关,先考虑2的边,把-1的边缩起来,直接把A放到C上,这样就相当于B连向A,把A取反,于是问题变为计算这样的n个点的有标号的树的个数,两棵树不同当且仅当有一个点的深度或正负号不同。
发现正负号的问题比较麻烦,一个点会被所有儿子取反一次,然后被所有祖先的儿子取反一次。可以直接利用父亲的正负来推出儿子,但如果直接记录当前层有多少个正、负点的话,效率难以优化过 O ( n 5 ) O(n^{5}) O(n5)
但发现两颗仅存在正负号不同的点的树,将点与父亲做差分(即相同为1,不同为-1)后也不同,反之亦然。于是直接记录当前层差分为-1的点的个数,枚举下一层差分为-1,1的点数(不考虑下一层的儿子影响),可计算出下一层差分为-1的点数(考虑下一层的儿子影响,这里直接把所有的差距由1改为-1或-1改为1,因为同时把1改为-1且把1改为-1会被其它不同时改的方案记录到),效率为 O ( n 4 ) O(n^{4}) O(n4)
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P=1e9+7;
const int N=55;
int n; ll c[N][N],f[N][N];
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n; c[0][0]=1;
	for(int i=1;i<=n;i++){
		c[i][0]=1;
		for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%P;
	}
	f[1][0]=f[1][1]=n;
	for(int i=1;i<n;i++) for(int j=0;j<=i;j++){
		//printf("i=%d j=%d f=%lld\n",i,j,f[i][j]);
		for(int x=0;x<=n-i;x++) for(int y=0;x+y<=n-i;y++) if(x+y>=max(j,1) && !((x+y-j)&1)){
			(f[i+x+y][abs(j+(x+y-j)/2-x)]+=f[i][j]*c[n-i][x+y]%P*c[x+y][x])%=P;
			//cout<<x<<" "<<y<<" "<<abs(j+(x+y-j)/2-x)<<endl;
		}
	}
	cout<<f[n][0]<<endl;
	return 0;
}

感觉最近做题缺乏自信,很多本来自己有能力想出来的东西却去看了题解,这样对思维的锻炼是大打折扣的,还是要鼓励自己独立思考吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值