DP题目总结

E. Game with Cards

问题描述

起初,Bob左右手分别拿了一张大小为0的卡牌,然后进行q次询问,在第 i 次询问中,Alice 给Bob 一张大小为ki的卡牌去替换Bob左手或右手的卡牌,替换完要求左手牌的大小在al,i~bl,i之间,右手牌的大小在ar,i ~ br,i之间,问是否可以满足全部要求,并输出每一次询问将牌给了左手还是右手。


题目分析

由于我们只将牌给左手或者右手,并且必须给二者其中之一,所以答案可以看成一段0,一段1,因此我们可以设计dp[i][0/1],表示在第i个位置,我们开始将牌给了左手/右手,然后后面的i+1,i+2……j我们将牌给了左手/右手,也就是i是我们0(1)段的开始位置。假设这一段0(1)的区间为[l,r],我们必须满足以下条件:对于所有的i属于[l,r] a0,i<=ki<=bi,0 且 a1,i<=kl-1<=b1,i。
由于我们的i是从后面转移过来,因此我们倒着处理。
在这里插入图片描述
对于绿色方块,我们就要满足a0,i<=ki<=b0,i,对于黄色方块,要满足a1,i<=ai-1<=b1,i。
假如二者都满足条件,我们就会更新dp[i][0],i是绿色方块中第一个0的位置。
因此,对于dp[i][0/1],我们可以从dp[j][1/0]转移过来(j>i),假如所有的 i∈[i,j-1]都满足a0,i<=ki<=b0,i 并且 a1,i<=ai-1<=b1,i就可以从j转移到i,所以我们只需要知道离i最近的满足条件 j 就可以。


代码:

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#pragma GCC optimize(3)
using namespace std;
const int N=1e5+5;
int n,m,mn[2],l[N][2],r[N][2],L[2],R[2],a[N],fir[2],both[2],nxt[N][2];
void solve(){
	cin>>n>>m;
	mn[1]=mn[0]=n+1,fir[0]=fir[1]=1,L[0]=L[1]=0,R[0]=R[1]=m;
	/*mn表示后面最近0/1的位置,fir表示,当前位置是否满足第一个条件
	L表示从上一位置过来中最大的L,R同理,这样k_i如果满足[L,R]的区间就满足了后面一段的区间 
    */
	for(int i=1;i<=n;i++)
		cin>>a[i]>>l[i][0]>>r[i][0]>>l[i][1]>>r[i][1];
	for(int i=n;i>=1;i--){
		if(a[i]>=l[i][0]&&a[i]<=r[i][0])fir[0]&=1; else fir[0]&=0;
		if(a[i]>=l[i][1]&&a[i]<=r[i][1])fir[1]&=1; else fir[1]&=0;
		//判断是否满足第一个条件
		L[0]=max(L[0],l[i][0]),R[0]=min(R[0],r[i][0]);
		L[1]=max(L[1],l[i][1]),R[1]=min(R[1],r[i][1]);
		//缩小区间
		if(fir[0]&&a[i-1]>=L[1]&&a[i-1]<=R[1])both[0]=1;else both[0]=0;
		if(fir[1]&&a[i-1]>=L[0]&&a[i-1]<=R[0])both[1]=1;else both[1]=0;
		//判断两个条件是否都满足
		if(both[0])nxt[i][0]=mn[1];//0从最近1转移过来
		if(both[1])nxt[i][1]=mn[0];
		if(both[0])mn[0]=i,fir[1]=1,L[0]=0,R[0]=m;
		//最近的满足条件的0的位置更新,然后从i-1开始就是1的连续子段,我们就1判断是否满足条件1,然后0是否满足条件二
	    if(both[1])mn[1]=i,fir[0]=1,L[1]=0,R[1]=m;
		
	}
	if(mn[0]>1&&mn[1]>1){
		cout<<"NO"<<endl;
		return;
	}
	else{
		cout<<"YES"<<endl;
		int pos=0;
		if(mn[0]>1)pos=1;
		for(int i=1;i<=n;i=nxt[i][pos],pos^=1)
			for(int j=i;j<nxt[i][pos];j++)
				cout<<pos<<" ";
		cout<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
		solve();
	return 0;
}

类似题目

HDU6800


E. Rescue Niwen!

问题描述

给你一个字符串s1s2s3s4……sn,然后这些字符串产生一些子字符串,s1 , s1s2, …, s1s2…sn, s2, s2s3, …, s2s3…sn, s3, s3s4, …, sn−1sn, sn.比如,字符串"abcd"产生’a’, ‘ab’, ‘abc’, ‘abcd’, ‘b’, ‘bc’, ‘bcd’, ‘c’, ‘cd’, ‘d’.
问能找到的最长上升子序列的长度。一个字符串a比b小:1.a是b的前缀并且a!=b 2,在a与b第一个不同的位置,a的字母在字母表中出现比b早。


题目分析:

假如s[i]大于s[j],我们就可以直接转移,但是如果相同的话,我们就要找他们第一个不同的位置k,然后dp[i]=max(dp[i],dp[j]+n-k+1),因此,我们要预处理 f[i][j] 表示位置i与j第一个不同的地方,f[i][j]=(s[i]==s[j])?0:f[i+1][j+1]+1


代码:

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#pragma GCC optimize(3)
using namespace std;
const int N=5e3+5;
int n,f[N],lcp[N][N];
string s;
void solve(){
	cin>>n>>s;
	s="#"+s;
	for(int i=1;i<=n;i++)
		lcp[i][n+1]=lcp[n+1][i]=0,f[i]=n-i+1;
	for(int i=n;i>=1;i--)
		for(int j=n;j>=1;j--)
			lcp[i][j]=(s[i]==s[j])?lcp[i+1][j+1]+1:0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(i+lcp[i][j]<=n&&s[j+lcp[i][j]]<s[i+lcp[i][j]])
				f[i]=max(f[i],f[j]+n-(i+lcp[i][j])+1);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		ans=max(ans,f[i]);
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--)
		solve();
	return 0;
}

E2. Rubik’s Cube Coloring (hard version)

问题描述

给你一个k层的二叉树,每个点都染一个颜色,但是每种颜色都有限制不能与某些颜色相邻,给了n个已经有颜色的点,问有多少种染色方法。


题目分析:

我们发现n<=2000&&k<=60,说明最多会有2000*60个点可能需要操作,所以我们可以树上dp,dp[i][j]表示以i为根的子树在i点染成j的情况下有多少种染色方法。然后dp[i][j]+=dp[i<<1][k1]*dp[i<<1|1][k2],在考虑下限制。


代码:

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#pragma GCC optimize(3)
using namespace std;
const int N=2e5+5;
const int mod=1e9+7;
int m,n,dp[N][10],tot,cnt[105],num[105];
map<int,int>col,id;
map<string,int> mp;
set<int> v;
int d(int x){
	int res=0;
	while(x){
		x/=2;
		res++;
	}
	return res;
}
int ksm(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
void init(){
	cnt[0]=1;
	for(int i=1;i<=m;i++)
		cnt[i]=cnt[i-1]*2;
	for(int j=1;j<=m;j++){
		int ans=1;
	    for(int i=2;i<=j;i++)
	    	ans=ans*ksm(4,cnt[i-1])%mod;
		num[j]=ans;
	}
}
void solve(){
	cin>>m>>n;
	init();
	mp["yellow"]=1;mp["orange"]=2;mp["green"]=3;
	mp["blue"]=4;mp["red"]=5;mp["white"]=6;
	for(int i=1;i<=n;i++){
	 	string s;int x;
		cin>>x>>s;
		col[x]=mp[s];
		while(x){
			v.insert(x);
			x>>=1;
		}
	}
	for(auto i:v)
		id[i]=++tot;
    auto it=v.end();it--;
	for( ;;it--){
		int x=*it,l,r,tmp=num[m-d(x)];
		if(d(x)!=m){
			for(int i=1;i<=6;i++){
				for(int j=1;j<=6;j++){
					if(i==j||i+j==7)continue;
					for(int k=1;k<=6;k++){
						if(i==k||i+k==7)continue;
						if(v.count(x<<1))l=dp[id[x<<1]][j];
						else l=tmp;
						if(v.count(x<<1|1))r=dp[id[x<<1|1]][k];
						else r=tmp;
						dp[id[x]][i]=(dp[id[x]][i]+l*r%mod)%mod;
					}
				}
			}
		}
		else	
			for(int i=1;i<=6;i++)
				dp[id[x]][i]=1;
		if(col.count(x)){
			for(int i=1;i<=6;i++)
				if(col[x]!=i)
					dp[id[x]][i]=0;
		}
		if(it==v.begin())break;
	}
	int ans=0;
	for(int i=1;i<=6;i++)
		ans=(ans+dp[id[1]][i])%mod;
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
		solve();
	return 0;
}

E. William The Oblivious

问题描述

给一个仅含abc的字符串,每次操作可以修改某一位的字符。
每次操作后,要求输出最少删除多少个数可以使字符串中不含有子序列abc。


题目分析:

由于我们需要多次修改和查询,所以我们可以建立一棵线段树,分别维护a[],b[],c[],ab[],bc[],abc[]表示使得区间内没有a,b,c,ab,bc,abc最少需要删除的字符数,
a[x]=a[x<<1]+a[x<<1|1];
b[x]=b[x<<1]+b[x<<1|1];
c[x]=c[x<<1]+c[x<<1|1];
ab[x]=min(a[x<<1]+ab[x<<1|1],ab[x<<1]+b[x<<1|1]);
bc[x]=min(b[x<<1]+bc[x<<1|1],bc[x<<1]+c[x<<1|1]);
abc[x]=min({a[x<<1]+abc[x<<1|1],ab[x<<1]+bc[x<<1|1],abc[x<<1]+c[x<<1|1]});


代码:

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#pragma GCC optimize(3)
using namespace std;
const int N=4e5+5;
struct segment_tree{
	string s;
	int a[N],b[N],c[N],ab[N],bc[N],abc[N];
	void update(int x){
		a[x]=a[x<<1]+a[x<<1|1];
		b[x]=b[x<<1]+b[x<<1|1];
		c[x]=c[x<<1]+c[x<<1|1];
		ab[x]=min(a[x<<1]+ab[x<<1|1],ab[x<<1]+b[x<<1|1]);
		bc[x]=min(b[x<<1]+bc[x<<1|1],bc[x<<1]+c[x<<1|1]);
		abc[x]=min({a[x<<1]+abc[x<<1|1],ab[x<<1]+bc[x<<1|1],abc[x<<1]+c[x<<1|1]});
	}
	void build(int x,int l,int r){
		if(l==r){
			if(s[l]=='a')a[x]=1,b[x]=c[x]=0;
			if(s[l]=='b')a[x]=c[x]=0,b[x]=1;
			if(s[l]=='c')a[x]=b[x]=0,c[x]=1;
			return;
		}
		int mid=l+r>>1;
		build(x<<1,l,mid);
		build(x<<1|1,mid+1,r);
		update(x);
	}
	void change(int x,int l,int r,int L,int R,char ch){
		if(l>R||r<L)return;
		if(l==r){
			s[l]=ch;
			if(s[l]=='a')a[x]=1,b[x]=c[x]=0;
			if(s[l]=='b')a[x]=c[x]=0,b[x]=1;
			if(s[l]=='c')a[x]=b[x]=0,c[x]=1;
			return;
		}
		int mid=l+r>>1;
		change(x<<1,l,mid,L,R,ch);
		change(x<<1|1,mid+1,r,L,R,ch);
		update(x);
	}
	int query(){
		return abc[1];
	}
}tr;
int n,q;
void solve(){
	cin>>n>>q;
	cin>>tr.s;
	tr.s="#"+tr.s;
	tr.build(1,1,n);
	while(q--){
		int pos;string s;
		cin>>pos>>s;
		tr.change(1,1,n,pos,pos,s[0]);
		cout<<tr.query()<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	solve();
	return 0;
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值