『DP』做题记录3

CF1063F

答案显然小于 \(\sqrt{2n}\)

而且最优情况下一定是长度连续递减。

所以可以 dp,\(f_{i,j}\) 表示答案为 \(i\)\([j,n]\)\(j\) 为开头是否可行。

字符串哈希可以做到 \(O(1)\) 转移。复杂度 \(O(n \sqrt{n})\)

AGC046D

发现一个当前操作后的状态可以用一个三元组 \((i,j,k)\) 表示 \((i,n]\) 这段后缀没被删除,有 \(j\)\(0\) 可以随便放,\(k\)\(1\) 可以随便放。

考虑到对于一个最终的 \(01\) 串可能对应多个三元组 \((i,j,k)\),考虑构造 \(01\) 串到 \((i,j,k)\) 的单射。

有一个比较自然的想法是拿最终 \(01\) 串的子序列和初始串的后缀做匹配。

假设匹配到 \((i,n]\),还剩下 \(j\)\(0\) 没有匹配,\(k\)\(1\) 没有匹配,那么这个 \(01\) 串单射到 \((i,j,k)\) 这个三元组。

不难发现这是满足单射定义的。

现在问题有两个:

  1. 如何算 \((i,j,k)\) 三元组可以被取到。
  2. 如何算有多少个 \(01\) 串单射到 \((i,j,k)\) 上。

实际上都是不难 dp 的,转移都是容易的,这部分留给读者思考。

Code:

ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>s+1;n=strlen(s+1);
g[0][0][0]=f[n][0][0]=1;
for(int i=1;i<=n;i++) for(int j=n;~j;j--) for(int k=n;~k;k--){
    g[i][j][k]|=g[i-1][j][k]|g[i][j+1][k]|g[i][j][k+1];
    if(i>1&&(s[i]=='0'||s[i-1]=='0')&&j) g[i][j][k]|=g[i-2][j-1][k];
    if(i>1&&(s[i]=='1'||s[i-1]=='1')&&k) g[i][j][k]|=g[i-2][j][k-1];
    if(j&&s[i]=='0') g[i][j][k]|=g[i-1][j-1][k+1];
    if(k&&s[i]=='1') g[i][j][k]|=g[i-1][j+1][k-1];
}
for(int i=n;i;i--) for(int j=0;j<=n;j++) for(int k=0;k<=n;k++){
    f[i-1][j][k]=(f[i-1][j][k]+f[i][j][k])%mod;
    if(s[i]=='1') f[i][j+1][k]=(f[i][j+1][k]+f[i][j][k])%mod;
    else f[i][j][k+1]=(f[i][j][k+1]+f[i][j][k])%mod;
}
int ans=0;
for(int i=0;i<=n;i++) for(int j=n;~j;j--) for(int k=n;~k;k--) if(g[i][j][k]) ans=(ans+f[i][j][k])%mod;
cout<<(ans+mod-1)%mod<<'\n';

CF1097H

问题很复杂的情况下,先考虑较为简单的情况。

讨论一下 \(|B|=1\) 怎么做:

\(f_{i,j}\) 表示 \(M_{+\infty}\) 的前缀 \([0,d^i)\) 集体加 \(j\) 的答案。

不难得出,\(f_{i,j}=\sum_{k=1}^{d} f_{i-1,(j+gen_{k}) \bmod \ a}\)

答案差分一下就可以求了。

\(|B|>1\),考虑类似 CF177G2 的做法,将串匹配分治做,对于子问题类似上述 dp 做即可,而问题在于两个子问题间对答案的贡献。

这个维护方式是很有启发性的,比较新鲜的维护方式。

就是考虑一个子问题内部维护两个数组,\(pre_i,suf_i\) 分别表示对于 \(b\) 数列去掉前 \(i\) 个元素和当前整个子问题的前缀是否能匹配,对于 \(b\) 数列之保留前 \(i\) 个元素和当前整个子问题的后缀是否能匹配

注意两个点:

  1. 这个状态是是否,也就是说如果我能通过位移等简单二进制运算维护,那么可以通过 bitset 优化复杂度。

  2. 这里匹配是指,对于一个前后缀和当前剩下的 \(b\) 序列按照顺序比较,若前后缀不够比较,通过补 \(0\) 比较至 \(b\) 全部剩下元素比较玩,而这里的比较则与题面中的比较方式无异。

这部分的贡献是容易合并的,具体合并方式自行手玩或者看代码。

且不难发现这部分合并是可以通过 bitset 优化的,复杂度 \(O(\dfrac{nmd\log_{d}r}{\omega})\)

代码之放下了数据维护的部分,其他内容容易写出。

Code:

struct Node{
	int ans,len;
	bitset<N> pre,suf;
}f[M][M];
inline Node operator +(Node a,Node b){
	if(!a.len) return b;
	Node res;res.ans=a.ans+b.ans;
	res.len=a.len+b.len;res.pre=a.pre,res.suf=b.suf;
	if(a.len<n-1) res.pre&=(b.pre>>a.len)|(e<<n-1-a.len);
	if(b.len<n-1) res.suf&=(a.suf<<b.len)|(e>>n-1-b.len);
	if(res.len>=n){
		bitset<N> u=a.suf&b.pre;
		if(a.len<n-1) u&=(e>>n-1-a.len);
		if(b.len<n-1) u&=(e<<n-1-b.len);
		res.ans+=u.count();
	}return res;
}

P3643

有点意思的题,思维难度不弱。

考虑如何直接满足递增的条件,为使得可以表述大小关系,我们将端点作为关键点,给原序列分为形如 \([p_i,p_{i+1})\) 的段,不难发现是 \(O(n)\) 段的,然后对于每个学校的 \(a,b\) 离散化。

而不属于同一段的点显然前面段内点小于后面段内的点,现在需要算的是一个长为 \(m\) 的段内放 \(n\) 个点,满足非零数递增条件。

考虑没有非零数的方案数,容易得出是:\(\dbinom{m}{n}\)

这个不妨枚举 \(0\) 的位置,这个答案就是 \(\sum\limits_{i=0}^{n} \dbinom{n}{i}\dbinom{m}{n-i}\)

这是个经典的范德蒙德卷积,这个其实就是 \(\dbinom{m+n}{n}\),具体证明不在这里阐述。

大力设计状态:\(f_{i,j}\) 表示第 \(i\) 所学校派出 \([p_j,p_{j+1})\) 区间内的游船,且其为区间内最大的的方案书数。

考虑从 \(f_{x,y}(x<i,y<j)\) 转移过来,设 \(n=\sum\limits_{k=x+1}^{i} [a_k \leq i <b_k],m=p_{i+1}-p_i\),注意现在应点了 \(i\) 必须在区间中,也就是必须有至少一个非 \(0\) 的位置。

也就是这时候的贡献系数应当是 \(\sum\limits_{i=0}^{n-1} \dbinom{n-1}{i}\dbinom{m}{n-i}=\sum\limits_{i=0}^{n} \dbinom{n-1}{i}\dbinom{m}{n-i}=\dbinom{m+n-1}{n}\)

转移方程就是 \(f_{i,j}=\sum\limits_{x<i} \sum\limits_{y<j} f_{x,y} \dbinom{m+n-1}{n}\)

用前缀和技巧就可以做到 \(O(n^3)\)

ARC118E

组合意义不好考虑的情况下,可以先考虑形式化答案。

不妨设某一个合法的障碍点集为 \(P\),设题目给定的障碍点集为 \(Q\),一条合法路径为 \(S\)

那么答案即为:\(\sum\limits_{S} \sum\limits_{Q \subseteq P} [S \bigcap P=\varnothing]\)

经典二项式定理容斥出来:\(\sum\limits_{S} \sum\limits_{Q \subseteq P} \sum\limits_{T\subseteq S \bigcap P} (-1)^{|T|}=\sum\limits_{S}\sum\limits_{T\subseteq S} (-1)^{|T|} \sum\limits_{Q\bigcup T \subseteq P} 1=\sum\limits_{S}\sum\limits_{T\subseteq S} (-1)^{|T|} (n-|Q\bigcup T|)!\)

注意:这里 \(\sum\limits_{Q\bigcup T \subseteq P} 1=(n-|Q\bigcup T|)!\) 当且仅当 \(Q\bigcup T\) 是一个合法的障碍集合,所以算的时候要判断 \(Q\bigcup T\) 是否为合法障碍。

考虑通过 dp 算出最后面的那个式子把容斥系数塞到 dp 里算即可,设 \(f_{i,j,k,0/1,0/1}\) 表示当前 \(S\) 到达了 \((i,j)\)\(|Q\bigcup T|=k\) 的答案,转移的话就考虑往 \((i+1,j)/(i,j+1)\) 转移即可,转移是容易的,不讲,\(O(n^3)\)

Code:

f[0][0][tot][0][0]=1;
for(int i=0;i<=n+1;i++) for(int j=0;j<=n+1;j++) for(int k=0;k<=n;k++) for(int p=0;p<2;p++) for(int q=0;q<2;q++){
    if(i<=n){
        (f[i+1][j][k][0][q]+=f[i][j][k][p][q])%=mod;
        if(!q&&i<n&&1<=j&&j<=n&&(a[i+1]==j||(a[i+1]==-1&&!vis[j])))
            (f[i+1][j][k+(a[i+1]!=j)][1][1]+=mod-f[i][j][k][p][q])%=mod;
    }
    if(j<=n){
        (f[i][j+1][k][p][0]+=f[i][j][k][p][q])%=mod;
        if(!p&&j<n&&1<=i&&i<=n&&(a[i]==j+1||(a[i]==-1&&!vis[j+1])))
            (f[i][j+1][k+(a[i]!=j+1)][1][1]+=mod-f[i][j][k][p][q])%=mod;
    }
}

P8820

这题赛时做不出,说明我当时太嫩了。

路径拍到序列上,dp 的,容易的可以想到的状态 \(f_{i,j\in[0,2]}\) 表示最后一次选在距离 \(i\)\(j\) 的最小答案。

考虑可能走到路径外,那么就同理转移,贪心选取最小儿子,设 \(g_i\) 表示所有 \(i\) 连向的点权值最小值。

\(f_{u,0}=\min \{f_{v,0},f_{v,1},f_{v,2}\}+c_u\)

\(f_{u,1}=\min \{f_{v,0},f_{v,1}+g_u\}\)

\(f_{u,2}=f_{v,1}\)

写成广义矩阵倍增算就没了!复杂度 \(O(3^3n \log n+3^3q\log n)\)

CF1038F

好题。

\(n,m\) 分别为 \(t,s\) 的长度。

直接考虑计数区间或后缀拼前缀方案数显然错。

错在哪?算重,容斥。

首先必须至少包含一个 \(s\),考虑应定 \([n-m+2,n]\cup\{1\}\)\(s\) 串匹配。

问题在于对于原问题,我们需要算的是每个位置作为序列从左往右的第一个匹配 \(s\) 串末尾的答案。

考虑算出 \(1\) 为第一个匹配 \(s\) 串末尾,\(p\) 为最后一个匹配 \(s\) 串末尾的答案。

注意此时将整个匹配序列向右平移后,答案不变,直到最后一个位置平移超出 \(n\)(相当于回到 \(1\)) 前,都会对答案产生贡献。

所以应定 \(1\) 为开头后,对答案的贡献应当乘上 \((n-p)\)

怎么算 \(1\) 为第一个匹配 \(s\) 串末尾,\(p\) 为最后一个匹配 \(s\) 串末尾的答案。

\(f_{i}\) 表示填了 \([1,i]\)\(i\) 匹配 \(s\) 串末尾方案数。

最后乘上去除应定 \(1\) 对序列结尾的一些位置,剩下位置随便填的贡献。

复杂度 \(O(n^2)\)

#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
#pragma GCC optimize("Ofast","unroll-loops","inline")
#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int N=100+20,M=1e6+20,mod=998244353;
int n,m,pw[N],f[N];char s[N];bool fl[N];
inline bool chk(int k){
	for(int i=1;i<=k;i++) if(s[i]!=s[m-k+i]) return 0;
	return 1;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>(s+1);m=strlen(s+1);
	for(int i=1;i<=m;i++) fl[i]=chk(m-i);
	pw[0]=1;for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2;
	f[1]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++) if(i-j>m||fl[i-j]){
			if(i>=n-m+1+1&&!fl[m-(i-n+m-1)]) continue;
			int d=i-j-m;
			f[i]=f[i]-(d>0?pw[d]:1)*f[j];
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int d=n-i-m+1;
		int res=(d>0?pw[d]:1)*f[i];
		ans=(ans+res*(n-i+1));
	}
	cout<<ans<<'\n';
	return 0;
}

CF776G

这是 2log 做法。

典题数位 dp,考虑找 \(f(x)<x\) 充要条件。

显然的设 \(k= \max d\),不难发现当且仅当,\(x\) 在二进制第 \(k\) 位为 \(1\) 时满足。

恰好等于 \(k\) 不好算,容斥一下算 \(\leq k\) 减去 \(<k\)

\(f_{i,0/1}\) 表示前 \(i\) 位是否打破上界限制,每个 \(16\) 进制位 \(\leq k\) 且在二进制第 \(k\) 位为 \(1\) 时的方案数,\(g_{i,0/1}\) 表示前 \(i\) 位是否打破上界限制,每个 \(16\) 进制位 \(< k\) 且在二进制第 \(k\) 位为 \(1\) 时的方案数。

复杂度 \(O(n \log^2 n)\)

#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
#pragma GCC optimize("Ofast","unroll-loops","inline")
#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int LGN=16;
char L[LGN+10],R[LGN+10];int f[LGN+10][2],g[LGN+10][2],mx[LGN+10],v[LGN+10][4];
int calc(int x){
	if(!~x) return 0;
	for(int i=0;i<LGN;i++) mx[i]=x&15,x>>=4;
	int res=0;
	for(int d=0;d<LGN;d++){
		int p=d/4,q=d&3;
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
		f[16][1]=1;g[16][1]=1;
		for(int i=LGN-1;~i;i--){
			for(int s=0;s<2;s++){
				int lim=s?min(d,mx[i]):d;
				if(i==p){
					if((lim>>q&1)&&s){
						f[i][0]=f[i][0]+f[i+1][s]*(v[lim][q]-1),f[i][mx[i]==lim]=f[i][mx[i]==lim]+f[i+1][s];
					}
					else f[i][0]=f[i][0]+f[i+1][s]*v[lim][q];
				}
				else{
					if(s) f[i][0]=f[i][0]+f[i+1][s]*lim,f[i][mx[i]==lim]=f[i][mx[i]==lim]+f[i+1][s];
					else f[i][0]=f[i][0]+f[i+1][s]*(lim+1);
				}
			}
		}
		if(d) for(int i=LGN-1;~i;i--){
			for(int s=0;s<2;s++){
				int lim=s?min(d-1,mx[i]):d-1;
				if(i==p){
					if((lim>>q&1)&&s){
						g[i][0]=g[i][0]+g[i+1][s]*(v[lim][q]-1),g[i][mx[i]==lim]=g[i][mx[i]==lim]+g[i+1][s];
					}
					else g[i][0]=g[i][0]+g[i+1][s]*v[lim][q];
				}
				else{
					if(s) g[i][0]=g[i][0]+g[i+1][s]*lim,g[i][mx[i]==lim]=g[i][mx[i]==lim]+g[i+1][s];
					else g[i][0]=g[i][0]+g[i+1][s]*(lim+1);
				}
			}
		}
		res+=(f[0][0]+f[0][1])-(!d?0:g[0][0]+g[0][1]);
	}
	return res;
}
void solve(){
	memset(L,0,sizeof(L));
	memset(R,0,sizeof(R));
	cin>>L>>R;
	int n=strlen(L),m=strlen(R);
	reverse(L,L+n);reverse(R,R+m);
	int l=0,r=0;
	for(int i=LGN-1;~i;i--) if(L[i]) l=(l<<4)+(L[i]<='9'?L[i]-'0':L[i]-'a'+10);
	for(int i=LGN-1;~i;i--) if(R[i]) r=(r<<4)+(R[i]<='9'?R[i]-'0':R[i]-'a'+10);
	int tot=calc(r)-calc(l-1);
	return cout<<tot<<'\n',void();
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	for(int p=0;p<4;p++) for(int i=1;i<LGN;i++) v[i][p]=v[i-1][p]+(i>>p&1);
	int T;cin>>T;
	while(T--) solve();
	return 0;
}

qoj7606

考虑暴力以外性质较少,只有一个只关心上一个必败态位置的性质。

那就只用这个性质优化 dp,设 \(f_{i,j,k}\) 表示,考虑第 \(i\) 位以及之前的数位和为 \(j\) 且,设当前为 \(a\) 之前必败态为 \(a-k+1\),考虑当前为 \(a+10^i\) 时候,上一个必败态距离 \(a+10^i\) 多远。

复杂度 \(O(10^3 \log ^3 n+T10 \log n)\)

#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
#pragma GCC optimize("Ofast","unroll-loops","inline")
#include<bits/stdc++.h>
#define ll long long
//#define int ll
using namespace std;
const int N=22,S=10,M=1e6+20,mod=998244353;
const int mx=18*9;
int f[N][N*S][N*S],a[N];ll n;
void solve(){
	cin>>n;n++;
	int m=0,p=1,s=0;
	while(n) a[m++]=n%10,n/=10;
	for(int i=m-1;~i;i--){
		for(int j=0;j<a[i];j++) p=f[i][s+j][p];
		s+=a[i];
	}
	p<=1?cout<<"Bajtek\n":cout<<"Algosia\n";
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	memset(f,-1,sizeof(f));
	for(int i=0;i<=mx;i++) for(int j=1;j<=mx;j++) f[0][i][j]=j<=i?j+1:1;
	for(int k=1;k<=18;k++){
		for(int i=0;i<=9*(18-k);i++) for(int j=1;j<=9*18;j++){
			for(int t=0,&x=f[k][i][j]=j;t<=9;t++){
				x=f[k-1][i+t][x];
			}
		}
	}
	int T;cin>>T;
	while(T--) solve();
	return 0;
}

P9746

一般。

区间 dp,\(f_{i,j}\) 表示这个区间的数可否被操作到只剩一个数。

转移枚举 \(3\) 个子区间表示这三个区间合并后的数作为选择的 \(i,j,k\)

可以容易做到 \(O(Tn^6)\)

\(w(l,r)\) 表示 \([l,r]\) 的异或和。

考虑优化,设:

\(g_{l,x}= \mathrm {arg}\min\limits_{r \geq l} \{ \exists p<k,p,k\in[l,r) ,f_{l,k}\ \mathrm{and} \ f_{k,r} \ \mathrm{and}\ ( w(l,k) \ \mathrm{xor}\ w(p,r) =x )\}\)

可得:

\(f_{l,r}= \mathrm{or} _{i=l+1}^{r} \{ [g_{l,s_r \ \mathrm{xor} \ s_{i-1}}<i] \}\)

\(g\) 不好求,考虑继续优化:

\(h_{l,x}= \mathrm {arg}\min\limits_{r \geq l} \{ f_{l,r} \ \mathrm{and}\ w(l,r)=x \},q_{l,x}=\min\limits_{k \geq l} \{ h_{k,x}\}\)

对于 \(h,q\) 是好求的,考虑如何求 \(g\)

\(g_{l,x}=\min\limits_{s=0}^{2^9-1}\{q_{h_{l,s}+1,s \ \mathrm{xor} \ x} \}\)

上述所有都是容易求解的,复杂度 \(O(Tn^2A)\)

构造方案直接在 dp 的时候记录是从哪里转移的即可。

最后直接递归求解方案。

Code:

#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
#pragma GCC optimize("Ofast","unroll-loops","inline")
#include<bits/stdc++.h>
#define ll long long
//#define int ll
#define pb push_back 
#define i16 unsigned short
using namespace std;
const int N=5e2+20,Mx=512;
bool f[N][N];i16 n,a[N],s[N],h[N][N],q[N][N],g[N][N],pg[N][N],pf[N][N];
struct node{int p1,p2,p3;};
vector<node> ans;
i16 calc(i16 l,i16 r,i16 d){
	if(l==r) return 0;
	i16 R=pf[l][r];i16 L=pg[l][s[r]^s[R-1]];
	i16 S=0,T=0,u=s[r]^s[R-1]^s[L]^s[l-1];
	for(S=L+1;S<R;S++){
		for(T=S;T<R;T++) if(f[S][T]&&((s[T]^s[S-1])==u)) break;
		if(T<R&&f[S][T]&&((s[T]^s[S-1])==u)) break;
	}
	i16 p1=0,p2=0,p3=0,res=0;
	p1=l-d;res+=calc(l,L,d),d+=L-l;
	p2=S-d;res+=calc(S,T,d),d+=T-S;
	p3=R-d;res+=calc(R,r,d),d+=r-R;
	ans.pb((node){p1,p2,p3});
	return res+1;
}
void solve(){
	cin>>n;ans.clear();
	s[0]=0;for(int i=1;i<=n;i++) cin>>a[i],s[i]=s[i-1]^a[i];
	for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) f[i][j]=0;
	for(int i=1;i<=n+1;i++) for(int j=0;j<Mx;j++) h[i][j]=q[i][j]=g[i][j]=n+1,pg[i][j]=pf[i][j]=0;
	for(int l=n;l;l--){
		for(int x=0;x<Mx;x++) q[l][x]=q[l+1][x];
		for(int r=l;r<=n;r++){
			if(l==r) f[l][r]=1;
			else{
				for(int i=l+1;i<=r;i++){
					if(f[l][r]) break;
					if(f[i][r]&&g[l][s[r]^s[i-1]]<i) f[l][r]=1,pf[l][r]=i;
				}
			}
			if(f[l][r]){
				i16 w=s[r]^s[l-1],lst=h[l][w];
				h[l][w]=min(h[l][w],(i16)r);
				q[l][w]=min(h[l][w],q[l][w]);
				if(lst==n+1) for(int x=0;x<Mx;x++){
					if(h[l][w]<g[l][x]&&q[h[l][w]+1][w^x]<g[l][x]) g[l][x]=q[h[l][w]+1][w^x],pg[l][x]=h[l][w];
				}
			}
		}
	}
	if(!f[1][n]) return cout<<"Shuiniao\n",void();
	cout<<"Huoyu\n";cout<<calc(1,n,0)<<'\n';
	for(node v:ans) cout<<v.p1<<' '<<v.p2<<' '<<v.p3<<'\n';
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int T;cin>>T;
	while(T--) solve();
	return 0;
}
posted @ 2023-10-27 15:10  Detect-Perplexity  阅读(10)  评论(0编辑  收藏  举报