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;
}
类似题目
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;
}