dp何帆佑 做题笔记

知识点

1.决策单调性优化 \(dp\)

决策单调性优化 \(dp\) ,主要用于优化 \(1D/1D\)\(dp\),比如类似于 \(dp_i=min_{j=1}^{i-1}dp_j+w(j,i)\) 或是 \(dp_i=min_{j=1}^{i-1}g_j+w(j,i)\),其中 \(w\) 是一个具有四边形不等式的式子,具体来说,需要满足对任意 \(l<l'<r'<r\) 都有 \(w(l,r')+w(l',r)<=w(l,r)+w(l',r')\) ,翻译成人话就是“相交优于包含”。

其写法有两种,一种是离线的,形如 \(dp_i=min_{j=1}^{i-1}g_j+w(j,i)\) ,可以跑低级递归,例如 CF868F Yet Another Minimization Problem

另一种是在线的,形如 \(dp_i=min_{j=1}^{i-1}dp_j+w(j,i)\),需要实时更新 \(dp\) 值,可以用高级递归,即 \(LARSCH\) 算法,例如 CFGYM10294B Dispatch money

其实只需要掌握下面的算法就足够了。

2. \(wqs\) 二分

\(wqs\) 二分用于优化一些具有凸性的 \(dp\) ,最难的点应该是看出有凸性,具体特征是要求恰好选 \(k\) 个。模板题没写,不过可以看一下[P6246 IOI 2000] 邮局 加强版 加强版 - 是个 \(wqs\) 二分+决策单调性优化 \(dp\) 的题,大概可以看作是板子题。它的具体做法是二分斜率,然后通过判断与凸包上的切点与需要选的点的个数做对比,由于随着斜率单调递增,切到的点也单调递增,所以他有单调性,可以二分。至于找切点,可以通过截距大小来判断是否是切点。拿下凸包举例。此时切点处应满足截距最小。设截距为 \(b\),则 \(dp_i\) 可以被表示为 \(dp_i=k\times i+b\) 其中 \(k\) 是二分的斜率。所以最小截距即为 \(min_{i=1}^{n}dp_i-k\times i\) 。例如上面那个题,先发现原式子在 \(i\) 相等时对 \(j\) 具有凸性,然后用 \(wqs\) 二分优化掉 \(j\) 那一维,就是将原来的 \(dp\) 式子改一下,改为 \(dp_i=min_{j=1}^{i-1}dp_j+w(j,i)-k\) 即每分一段还需额外花费 \(k\) 的花费。这样就保证了划分为 \(i\) 段的答案会减少 \(i\times k\)

3.\(slope\space trick\)

\(slope\space trick\) 真的就是个 \(trick\) ,它是对于 \(dp\) 中的一位维护一个凸函数,然后枚举 \(i\) 每次可以用很少量的操作来更改这个凸包的 \(trick\),他有两种维护方法,一种是维护拐点,另一种是维护斜率。维护斜率的没写过,这里着重说一下维护拐点的,它所维护的就是凸包上的拐点横坐标,并且每拐一次,斜率只会加一,他还需要维护起始点或终止点的纵坐标和斜率信息。拿将整个函数加绝对值函数 \(|a-x|\) 举例就是加入两个横坐标为 \(a\) 的拐点,原因是,这个点前面的地方斜率\(-1\) ,后面部分斜率 \(+1\) 。所以这个地方的斜率变化了两次,其余地方斜率的差值没变化。然后更新起始点\终止点的信息。大部分的题都会比较难写,这里人一个模板,HearthStone 。比较板的一个题目,用的是维护拐点。

4.\(DP\)\(DP\)

之前一直不会,现在来填个坑。它的实质是在自动机上跑 \(dp\) ,它的内层 \(dp\) 转移可以哈希压缩成一个数值,看作是自动机上的节点,然后内层 \(dp\) 的转移实际上就是在这些节点之间连边,要求是这一层的转移只跟上一层有关。外层 \(dp\) 实际上就是在这个自动机上跑 \(dp\) 。模板题是P10614 BZOJ3864 Hero meet devil ,这个题的内层 \(dp\) 是正常的求最长公共子序列长度的 \(dp\) ,因为这个 \(dp\) 只需要你知道 \(T\) 中上一个地方的 \(dp\) 值,外层则是对 \(T\) 的位置和当前自动机节点的转移,这个转移是容易的。但问题是自动机节点过多,所以我们考虑优化自动节点个数,我们发现这个 \(dp\) 相邻两位的差值只可能是 \(0\)\(1\) ,所以可以直接用二进制表示这个内层 \(dp\) 的差分数组做转移。

刷题

CF868F Yet Another Minimization Problem

没啥好说的,决策单调性板子,直接上代码。

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,inf=1e18;
int a[N],g[N],f[N],cnt[N],l=1,r=0,ret;
int calc(int L,int R)
{
	while(l>L) ret+=cnt[a[--l]]++;
	while(r<R) ret+=cnt[a[++r]]++;
	while(l<L) ret-=--cnt[a[l++]];
	while(r>R) ret-=--cnt[a[r--]];
	return ret;
}
void solve(int L,int R,int pl,int pr)
{
	int mid=(L+R)>>1,wl=pl,wr=min(mid,pr);
	int mn=inf,idx=0;
	for(int i=wl;i<=wr;i++)
	{
		int val=g[i-1]+calc(i,mid);
		if(val<mn) mn=val,idx=i;
	}
	f[mid]=mn;
	if(L==R) return;
	solve(L,mid,pl,idx);
	solve(mid+1,R,idx,pr);
}
signed main()
{
	int n,k;fin>>n>>k;
	for(int i=1;i<=n;i++) fin>>a[i];
	g[0]=0;f[0]=inf;
	for(int i=1;i<=n;i++) g[i]=inf;
	for(int i=1;i<=k;i++)
	{
		solve(1,n,1,n);
		for(int i=1;i<=n;i++) g[i]=f[i];
	}
	fout<<f[n];
	return 0;
}

CFGYM10294B Dispatch money

在线决策单调性板子,也没啥好说的。上代码。

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=300010,inf=1e18;
int p[N],f[N],n,x,a[N];
struct node
{
	int s[N],L=1,R,now;
	il void add(int x,int k)
	{
		for(int i=x;i<=n;i+=i&-i) s[i]+=k;
	}
	il int query(int x)
	{
		int ret=0;
		for(int i=x;i;i-=i&-i) ret+=s[i];
		return ret;
	}
	il int calc(int l,int r)
	{
		while(L>l)
		{
			L--;
			now+=query(a[L]);
			add(a[L],1);
		}
		while(R<r)
		{
			now+=R-L+1-query(a[R+1]);
			R++;
			add(a[R],1);
		}
		while(L<l)
		{
			add(a[L],-1);
			L++;
			now-=query(a[L-1]);
		}
		while(R>r)
		{
			add(a[R],-1);
			R--;
			now-=R-L+1-query(a[R+1]);
		}
		return now;
	}
} A,B;
il void checkA(int j,int i)
{
	int val=f[j]+A.calc(j+1,i)+x;
	if(val<f[i]) f[i]=val,p[i]=j;
}
il void checkB(int j,int i)
{
	int val=f[j]+B.calc(j+1,i)+x;
	if(val<f[i]) f[i]=val,p[i]=j;
}
il void solve(int l,int r)
{
	if(r-l<=1) return;
	int mid=(l+r)>>1;
	for(int i=p[l];i<=p[r];i++) checkA(i,mid);
	solve(l,mid);
	for(int i=l+1;i<=mid;i++) checkB(i,r);
	solve(mid,r);
}
signed main()
{
	fin>>n>>x;
	for(int i=1;i<=n;i++) fin>>a[i];
	if(n==1)
	{
		fout<<x;
		return 0;
	}
	if(n==2)
	{
		if(a[1]>a[2]) fout<<min(1ll+x,2*x);
		else fout<<x;
		return 0;
	}
	for(int i=1;i<=n;i++) f[i]=inf;
	for(int i=1;i<=n;i++) checkA(0,i);
	solve(0,n);
	fout<<f[n];
	return 0;
}

HearthStone

\(slope\space trick\) 板子,注意点写法,可以直接打全局标记,不需要用高级数据结构,推推式子结束,也没啥好说的。

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=1000010,inf=1e9;
int a[N],add,k,y,st[N],tp;
priority_queue<int> q1;
priority_queue<int,vector<int>,greater<int>> q2;
signed main()
{
	int n;fin>>n;
	for(int i=1;i<=n;i++) fin>>a[i];
	sort(a+1,a+1+n);
	q1.push(0),q2.push(inf);
	for(int i=1;i<=1e6;i++) q2.push(0);
	for(int i=1;i<=n;i++)
	{
		add++;
		y+=a[i];k--;
		if(a[i]>=q2.top()+add)
		{
			q2.push(a[i]-add);
			q2.push(a[i]-add);
			q1.push(q2.top()+add);
			q2.pop();
		}
		else
		{
			q1.push(a[i]);
			q1.push(a[i]);
			q2.push(q1.top()-add);
			q1.pop();
		}
	}
	while(!q1.empty()) st[++tp]=q1.top(),q1.pop();
	int ans=y;
	for(int i=tp;i>=2;i--)
	{
		ans+=k*(st[i-1]-st[i]);
		k++;
	}
	fout<<ans;
	return 0;
}
/*
slope trick板子,学一下
*/

[P6246 IOI 2000] 邮局 加强版 加强版

\(wqs\) 二分加决策单调性优化 \(dp\) ,难点在于发现决策单调性和凸性,这个题当时板子有点假,修了一下。

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=500010,inf=1e18;
int a[N],sum[N],f[N],n,m,cnt[N],p[N];
il int w(int l,int r)
{
	int mid=(l+r)>>1;
	if((r-l)&1) return sum[r]-sum[mid]-(sum[mid]-sum[l-1]);
	else return sum[r]-sum[mid]-(sum[mid-1]-sum[l-1]);
}
il void check(int j,int i,int k)
{
	int val=f[j]+w(j+1,i)-k;
	if(val<f[i]) p[i]=j,f[i]=val,cnt[i]=cnt[j]+1;
	else if(f[i]==val && cnt[j]+1>cnt[i]) cnt[i]=cnt[j]+1,p[i]=j;
}
il void solve(int l,int r,int k)
{
	if(r-l<=1) return;
	int mid=(l+r)>>1;
	for(int i=p[l];i<=p[r];i++) check(i,mid,k);
	solve(l,mid,k);
	for(int i=l+1;i<=mid;i++) check(i,r,k);
	solve(mid,r,k);
}
il void DP(int k)
{
	for(int i=1;i<=n;i++) f[i]=inf,p[i]=cnt[i]=0;
	for(int i=1;i<=n;i++) check(0,i,k);
	solve(0,n,k);
}
signed main()
{
	fin>>n>>m;
	for(int i=1;i<=n;i++) fin>>a[i];
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
	int l=-inf,r=0,ret=-inf;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		DP(mid);
		if(cnt[n]>=m) r=mid-1,ret=mid;
		else l=mid+1;
	}
	DP(ret);
	fout<<f[n]+ret*m;
	return 0;
}
/*
调皮单老哥,单调性在线也可以单老哥!!! 
*/

Chords - Problem

挺有意思的一个题。首先可以有一个区间 \(dp_{i,j}\) 表示区间 \([i,j]\) 里连边的最大值,转移时就分选端点和不选端点两种情况转移。复杂度是 \(O(n^2)\),正解是考虑用数据随机的特性来优化,发现数据随机时,答案会在 \(O(\sqrt{n})\) 周围浮动,所以考虑将答案计入状态来缩小枚举数量。记 \(dp_{i,j}\) 表示以 \(i\) 为右端点的区间,答案大于等于 \(j\) 的左端点最大值,转移分选右端点和不选右端点两种情况转移,不选时 \(dp_{i,j}=dp_{i-1,j}\) ,选的时候,设该线段左端点为 \(l_i\) ,则现在可以二分出一个 \(x\) ,满足 \(dp_{i-1,x}>l_i\) ,然后转移是 \(dp_{i,j}=dp_{l_i-1,j-x-1}\)

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,V=1010;
int lst[N],dp[V][N<<1];
signed main()
{
	int n;fin>>n;
	for(int i=1;i<=n;i++)
	{
		int a,b;fin>>a>>b;
		lst[b]=a;
	}
	for(int i=1;i<=2*n;i++) dp[0][i]=2*n;
	for(int i=1;i<=2*n;i++)
	{
		for(int j=1;j<=V-10;j++) dp[j][i]=dp[j][i-1];
		if(!lst[i]) continue;
		int l=0,r=V-10,ret=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(dp[mid][i-1]>lst[i]) l=mid+1,ret=mid;
			else r=mid-1; 
		}
		dp[ret+1][i]=max(dp[ret+1][i],lst[i]);
		for(int j=ret+2;j<=V-10;j++) dp[j][i]=max(dp[j][i],dp[j-ret-1][lst[i]-1]);
	}
	int l=0,r=V-10,ret=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(dp[mid][2*n]) l=mid+1,ret=mid;
		else r=mid-1; 
	}
	fout<<ret;
	return 0;
}
/*
利用好数据随机的特性
*/

CFGYM D.Digits

这个题纯纯 \(dirty\space work\)\(dp\) 毫无美感可言,对于回文串,可以考虑跑匹配,并且由于中心点不确定,所以应该是区间 \(dp\) ,又由于他的匹配过程中可能会有一端有多余字符,所以得再多记两维表示多出来的是哪一段的几位。又因为可能会分别在左边和右边多出来,所以还得分别记 \(dpl\)\(dpr\) 表示左边多和右边多,转移就是左边多在右边再选一个区间,右边多就在左边再选一个区间。但这是 \(O(n^4log^2V)\) 的,虽然看起来不太能过,但相信 \(CF\) 评测机实力。 其实还可以拿前缀和再搞搞,但那实在太屎了,不想写了。

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200,mod=1e9+9;
int dpl[N][N][N][10],dpr[N][N][N][10],a[N],sum[N];
il void upd(int& x,int y)
{
	x=(x+y>=mod)?x+y-mod:x+y;
}
signed main()
{
	int t;fin>>t;
	while(t--)
	{
		int n;fin>>n;
		for(int i=1;i<=n;i++) fin>>a[i],sum[i]=sum[i-1]+a[i];
		for(int i=1;i<=n;i++)
		{
			for(int j=i;j<=n;j++)
			{
				for(int k=0;k<=j-i+1;k++)
				{
					for(int f=0;f<=8;f++) dpl[i][j][k][f]=dpr[i][j][k][f]=0;
				}
			}
		}
		for(int len=1;len<=n;len++)
		{
			for(int i=1;i+len-1<=n;i++)
			{
				int j=i+len-1;
				string ss=to_string(sum[j]-sum[i-1]);
				for(int k=1;k<=(int)ss.size();k++)
				{
					string s1=ss.substr(0,k),s2=ss.substr(k,ss.size()-k);
					reverse(s1.begin(),s1.end());
					if(s1.size()>=s2.size())
					{
						if(s1.substr(0,s2.size())==s2) upd(dpl[i][j][len][s1.size()-s2.size()],1);
					}
					else
					{
						if(s2.substr(0,s1.size())==s1) upd(dpr[i][j][len][s2.size()-s1.size()],1);
					}
				}
				for(int k=0;k<(int)ss.size();k++)
				{
					string s1=ss.substr(0,k),s2=ss.substr(k+1,ss.size()-k-1);
					reverse(s1.begin(),s1.end());
					if(s1.size()>=s2.size())
					{
						if(s1.substr(0,s2.size())==s2) upd(dpl[i][j][len][s1.size()-s2.size()],1);
					}
					else
					{
						if(s2.substr(0,s1.size())==s1) upd(dpr[i][j][len][s2.size()-s1.size()],1);
					}
				}
				for(int k=1;k<=len;k++)
				{
					for(int f=0;f<=8;f++)
					{
						int val=dpl[i][j][k][f];
						if(val)
						{
							string s=to_string(sum[i+k-1]-sum[i-1]);
							s=s.substr(0,f);
							reverse(s.begin(),s.end());
							for(int x=j+1;x<=n;x++)
							{
								string t=to_string(sum[x]-sum[j]);
								if(s.size()<t.size())
								{
									if(t.substr(0,f)==s) upd(dpr[i][x][x-j][t.size()-s.size()],val);
								}
								else
								{
									if(s.substr(0,t.size())==t) upd(dpl[i][x][k][s.size()-t.size()],val);
								}
							}
						}
						val=dpr[i][j][k][f];
						if(val)
						{
							string s=to_string(sum[j]-sum[j-k]);
							s=s.substr(s.size()-f,f);
							for(int x=i-1;x>=1;x--)
							{
								string t=to_string(sum[i-1]-sum[x-1]);
								reverse(t.begin(),t.end());
								if(s.size()<=t.size())
								{
									if(t.substr(0,f)==s) upd(dpl[x][j][i-x][t.size()-s.size()],val);
								}
								else
								{
									if(s.substr(0,t.size())==t) upd(dpr[x][j][k][s.size()-t.size()],val);
								}
							}
						}
					}
				}
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++) upd(ans,dpl[1][n][i][0]);
		fout<<ans<<"\n";
	}
	return 0;
}
/*
回文串考虑从中间点开始跑匹配。
这题纯纯dirty work
转移理论可以通过前缀和做到O(n^3logV)
懒了,不想写,常数小点能过 
*/

[P10104 GDKOI2023 提高组] 异或图

容斥题。

首先考虑 \(m=0\) 的情况,及 P3447 [POI 2006] KRY-Crystals 。异或有个很优秀的性质 \(a\oplus b \oplus c=0\) 就相当于 \(a\oplus b=c\) 所以只要确定其中 \(n-1\) 个位置,那么第 \(n\) 个位置就确定了,只需要让这个值小于等于 \(a_i\) 就好了。可以从高位到低位考虑,设当前位 \(j\) 有数在当前位为 \(1\) ,但他在这一位取了 \(0\),那么那就可以立马求出答案。就是其他数可以随便选。否则就是全取的是 \(1\) ,那么就可以转为 \(j-1\) 位的子问题。对于这个题,要求的异或和是 \(C\) ,则可以在原数组中放入 \(C\) 求一遍,再放入 \(C-1\) 求一遍,两个值相减即为答案。

然后考虑原问题,我们可以考虑容斥,最基础的是对边容斥。

\[ans=\sum_{S}w(S)(-1)^{|S|} \]

其中 \(w(S)\) 表示当 \(S\) 集合中的边两边数值都相等时的答案,我们发现此时的图被分为了若干连通块,每一个连通块中的数值都相等,最大值限制是连通块中限制的最小值。由于对边容斥还是太吃操作了,所以我们改为对连通块容斥。

\[ans=\sum_{\Gamma}w(\Gamma)\sum_{E}[E划分出\Gamma](-1)^{|E|} \]

其中 \(w(\Gamma)\) 表示当连通块划分方案为 \(\Gamma\) 时的答案,可以修改为对于每一个连通块计算。

\[ans=\sum_{\Gamma}w(\Gamma)\prod_{i}\sum_{E\in e(\Gamma_i)}[E联通\Gamma_i](-1)^{|E|} \]

其中 \(\Gamma_i\) 为连通块划分方案 \(\Gamma\) 的子连通块, \(e(S)\)\(S\) 导出子图的边集。

\(f(S)=\sum_{E\in e(S)}[E联通S](-1)^{|E|}\)

我们发现这个东西可以用子集容斥来做。设 \(g(S)\) 表示不考虑联通的限制的答案。

\(g(S)=\sum_{i}\binom{|e(S)|}{i}(-1)^{i}\) ,这个式子当且仅当在 \(i=0\) 时取值为一,其他情况取值为 \(0\)

\(f(S)\) 的求法可以考虑枚举包含最大值的连通块做容斥。

\[f(S)=g(S)-\sum_{T}[T包含S中最大值]f(T)\times g(S/T) \]

那么现在就可以 \(O(3^n)\) 求出所有 \(f(S)\)。并且可以把原式转化为:

\[ans=\sum_{\Gamma}w(\Gamma)\prod_i f(\Gamma_i) \]

那么现在可以通过枚举连通块划分方案来达到 \(O(Bell(n))\) 的复杂度,可以过 \(n<=13\) 的数据,但这还不够,考虑继续优化。

我们发现每个连通块中只有 \(a_i\) 值最小的有用,并且还需要在奇连通块中才有用,将其称为关键点,所以我们可以将答案改为枚举关键点的形式,即:

\[ans=\sum_S w(S) \sum_\Gamma [S为\Gamma的关键点]\prod_if(\Gamma_i) \]

\(\sum_\Gamma [S为\Gamma的关键点]\prod_if(\Gamma_i)\) 设为 \(g(S)\) ,则容易发现有一种比较简单的做法是同时记录关键点集合和已选数集合做转移,但这样是 \(O(4^n)\) 的,故考虑继续优化。

对于一个位置,他前面表示这个数是不是关键点,他后面表示这个点有没有被选过。可以将转移改为每次枚举一个数 \(i\) 若它已经在前面关键点的选择中被选过了,那么它就不是关键点,直接将这一位归零转移,否则在后面没被选过的集合中选出一个子集做转移,此时当前点就是关键点。

所以就做完了,时间复杂度\(O(3^n+n3^n+2^nnlogV)\) 第一个是求 \(f(S)\) 的复杂度,第二个是求 \(g(S)\) 的复杂度,第三个是求答案的复杂度。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=20,mod=998244353;
int a[N],f[1<<15],g[1<<15],n,m,c,h[N][1<<15],b[N],cnt,ans,dp[N][2][2],id[N],mp[N];
struct edge
{
	int u,v;
} e[N*N];
void get_g()
{
	for(int s=1;s<(1<<n);s++)
	{
		int cnt=0;
		for(int i=1;i<=m;i++)
		{
			if((s>>(mp[e[i].u]-1)&1) && (s>>(mp[e[i].v]-1)&1)) cnt++;
		}
		if(!cnt) g[s]=1;
	}
}
void get_f()
{
	for(int S=1;S<(1<<n);S++)
	{
		f[S]=g[S];
		for(int T=S&(S-1);T>=1<<__lg(S);T=(T-1)&S)
		{
			f[S]=(f[S]+mod-f[T]*g[S^T]%mod)%mod;
		}
	}
}
void get_h()
{
	h[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int s=0;s<(1<<n);s++)
		{
			if(!h[i-1][s]) continue;
			if(s>>(i-1)&1)
			{
				h[i][s^(1<<(i-1))]=(h[i][s^(1<<(i-1))]+h[i-1][s])%mod;
			}
			else
			{
				int _s=(s>>i)^((1<<(n-i))-1);
				for(int t=_s;;t=(t-1)&_s)
				{
					int ss=(s&((1<<(i-1))-1))|(t<<i)|((s>>i)<<i);
					int now=(1<<(i-1))|(t<<i);
					if(__builtin_popcount(now)&1) h[i][ss|(1<<(i-1))]=(h[i][ss|(1<<(i-1))]+h[i-1][s]*f[now])%mod;
					else h[i][ss]=(h[i][ss]+h[i-1][s]*f[now]%mod*((a[id[i]]+1)%mod))%mod;
					if(!t) break;
				}
			}
		}
	}
}
void get_ans()
{
	for(int s=0;s<(1<<n);s++)
	{
		if(!h[n][s]) continue;
		cnt=0;
		for(int i=1;i<=n;i++) if(s>>(i-1)&1) b[++cnt]=a[id[i]];
		b[++cnt]=c;
		int tmp=0;
		for(int i=59;i>=0;i--)
		{
			memset(dp,0,sizeof(dp));
			dp[0][0][0]=1;int sum=0;
			for(int j=1;j<=cnt;j++)
			{
				sum^=b[j]>>(i+1);
				int val=(b[j]&((1ll<<i)-1))+1;val%=mod;
				if(b[j]>>i&1)
				{
					dp[j][0][0]=dp[j-1][0][1]*val%mod;
					dp[j][0][1]=dp[j-1][0][0]*val%mod;
					dp[j][1][0]=(dp[j-1][0][0]+dp[j-1][1][0]*((1ll<<i)%mod)+dp[j-1][1][1]*val)%mod;
					dp[j][1][1]=(dp[j-1][0][1]+dp[j-1][1][1]*((1ll<<i)%mod)+dp[j-1][1][0]*val)%mod;
				}
				else
				{
					dp[j][0][0]=dp[j-1][0][0]*val%mod;
					dp[j][0][1]=dp[j-1][0][1]*val%mod;
					dp[j][1][0]=dp[j-1][1][0]*val%mod;
					dp[j][1][1]=dp[j-1][1][1]*val%mod;
				}
			}
			if(!sum) tmp=(tmp+dp[cnt][1][0])%mod;
			if(!sum && i==0) tmp=(tmp+dp[cnt][0][0])%mod;
		}
		b[cnt]=c-1;
		for(int i=59;i>=0;i--)
		{
			memset(dp,0,sizeof(dp));
			dp[0][0][0]=1;int sum=0;
			for(int j=1;j<=cnt;j++)
			{
				sum^=b[j]>>(i+1);
				int val=(b[j]&((1ll<<i)-1))+1;val%=mod;
				if(b[j]>>i&1)
				{
					dp[j][0][0]=dp[j-1][0][1]*val%mod;
					dp[j][0][1]=dp[j-1][0][0]*val%mod;
					dp[j][1][0]=(dp[j-1][0][0]+dp[j-1][1][0]*((1ll<<i)%mod)+dp[j-1][1][1]*val)%mod;
					dp[j][1][1]=(dp[j-1][0][1]+dp[j-1][1][1]*((1ll<<i)%mod)+dp[j-1][1][0]*val)%mod;
				}
				else
				{
					dp[j][0][0]=dp[j-1][0][0]*val%mod;
					dp[j][0][1]=dp[j-1][0][1]*val%mod;
					dp[j][1][0]=dp[j-1][1][0]*val%mod;
					dp[j][1][1]=dp[j-1][1][1]*val%mod;
				}
			}
			if(!sum) tmp=(tmp+mod-dp[cnt][1][0])%mod;
			if(!sum && i==0) tmp=(tmp+mod-dp[cnt][0][0])%mod;
		}
		ans=(ans+tmp*h[n][s])%mod;
	}
}
bool cmp(int x,int y)
{
	return a[x]<a[y];
}
signed main()
{
	fin>>n>>m>>c;
	for(int i=1;i<=n;i++) fin>>a[i],id[i]=i;
	sort(id+1,id+1+n,cmp);
	for(int i=1;i<=n;i++) mp[id[i]]=i;
	for(int i=1;i<=m;i++) fin>>e[i].u>>e[i].v;
	get_g();get_f();get_h();get_ans();
	fout<<ans;
	return 0;
}

P9732 [CEOI 2023] Trade

首先设 \(w(l,r)\) 为选择 \([l,r]\) 这段区间的答案,不难发现原式满足四边形不等式。所以第一问可以用决策单调性很轻松求出。

对于第二问,我们考虑若有区间 \([l_l,r_1],[l_2,r_2]\) 满足 \(l_1<l_2<r_2<r_1\) 都是答案最大值,则由四边形不等式有 \(w(l_1,r_2)+w(l_2,r_1)\ge w(l_1,r_1)+w(l_2,r_2)=2\times ans\) ,所以区间 \([l_1,l_2]\),和区间 \([l_2,r_1]\) 的值均为答案最大值。那么 \([l_1,r_1]\)\([l_2,r_2]\) 可以被 \([l_1,r_2]\)\([l_2,r_1]\)\([l_2,r_1]\) 平替。我们发现在这样替换过后,对于可能满足条件的 \(r\) 他们所对应的 \(l\) 单调不减,所以可以使用双指针状物优化成为一个 \(n\) 次修改,每次将区间中 \(\ge w\) 的数赋值为 \(1\) ,其中 \(w\) 为区间 \(k\) 大值。我们可以对权值做扫描线,然后记个 \(set\) 来求答案。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=250010,inf=1e18;
int lsh[N],s[N],c[N],cnt,rt[N],tot,ans,p[N],sum[N],k,mx[N],vis[N];
vector<int> b[N];
vector<pair<int,int> > q[N];
struct node
{
	int sum,lid,rid,cnt;
} t[N<<5];
il void push_up(int id)
{
	t[id].sum=t[t[id].lid].sum+t[t[id].rid].sum;
	t[id].cnt=t[t[id].lid].cnt+t[t[id].rid].cnt;
}
il int add(int x,int l,int r,int idx,int k)
{
	int id=++tot;
	t[id]=t[x];
	if(l==r)
	{
		t[id].sum+=lsh[l]*k;
		t[id].cnt+=k;
		return id;
	}
	int mid=(l+r)>>1;
	if(idx<=mid) t[id].lid=add(t[x].lid,l,mid,idx,k);
	else t[id].rid=add(t[x].rid,mid+1,r,idx,k);
	push_up(id);
	return id;
}
il int query_sumkth(int u,int v,int l,int r,int k)
{
	if(l==r) return k*lsh[l];
	int mid=(l+r)>>1,x=t[t[v].rid].cnt-t[t[u].rid].cnt;
	if(x>=k) return query_sumkth(t[u].rid,t[v].rid,mid+1,r,k);
	else return query_sumkth(t[u].lid,t[v].lid,l,mid,k-x)+t[t[v].rid].sum-t[t[u].rid].sum;
}
il int query_kth(int u,int v,int l,int r,int k)
{
	if(l==r) return l;
	int mid=(l+r)>>1,x=t[t[v].rid].cnt-t[t[u].rid].cnt;
	if(x>=k) return query_kth(t[u].rid,t[v].rid,mid+1,r,k);
	else return query_kth(t[u].lid,t[v].lid,l,mid,k-x);
}
il int w(int l,int r)
{
	if(r-l+1<k) return -inf-10;
	int ret=-sum[r]+sum[l-1];
	ret+=query_sumkth(rt[l-1],rt[r],1,cnt,k);
	return ret;
}
il void upd(int j,int i)
{
	int val=w(j,i);
	if(val>mx[i]) mx[i]=val,p[i]=j;
	else if(val==mx[i]) p[i]=max(p[i],j);
}
il void solve(int l,int r)
{
	if(r-l<=1) return;
	int mid=(l+r)>>1;
	for(int i=p[l];i<=p[r];i++) upd(i,mid);
	solve(l,mid);
	for(int i=l+1;i<=mid;i++) upd(i,r);
	solve(mid,r);
}
signed main()
{
	int n;fin>>n>>k;
	for(int i=1;i<=n;i++) fin>>c[i],sum[i]=sum[i-1]+c[i];
	for(int i=1;i<=n;i++) fin>>s[i],lsh[i]=s[i];
	sort(lsh+1,lsh+1+n);
	cnt=unique(lsh+1,lsh+1+n)-lsh-1;
	for(int i=1;i<=n;i++) s[i]=lower_bound(lsh+1,lsh+1+cnt,s[i])-lsh;
	for(int i=1;i<=n;i++) rt[i]=add(rt[i-1],1,cnt,s[i],1);
	for(int i=1;i<=n;i++) mx[i]=-inf,p[i]=1;
	for(int i=k;i<=n;i++) upd(1,i);
	solve(0,n);
	int ans=-inf;
	for(int i=k;i<=n;i++) ans=max(ans,mx[i]);
	fout<<ans<<"\n";
	int idx=1;
	for(int i=k;i<=n;i++)
	{
		if(mx[i]==ans)
		{
			for(int j=idx;j<=p[i];j++)
			{
				if(w(j,i)==ans)
				{
					int val=query_kth(rt[j-1],rt[i],1,cnt,k);
					q[val].push_back({j,i});
				}
			}
			idx=p[i];
		}
	}
	for(int i=1;i<=n;i++) b[s[i]].push_back(i),vis[i]=1;
	set<int> st;
	for(int i=cnt;i>=1;i--)
	{
		for(int j:b[i]) st.insert(j);
		for(auto j:q[i])
		{
			int l=j.first,r=j.second;
			auto tmp1=st.lower_bound(l),tmp2=st.upper_bound(r);
			st.erase(tmp1,tmp2);
		}
	}
	for(int i:st) vis[i]=0;
	for(int i=1;i<=n;i++) fout<<vis[i];
	return 0;
}
/*
决策单调性+小清新双指针找合法位置 
*/

CFGYM C.Assign or Multiply

首先看到题发现乘法应该并不好做,由于模数是质数,所以可以考虑求出原根后转化为加法,那么现在的问题就转化为了一个模意义下的可行性背包问题。我们发现此时可以直接暴力寻找不同位置,发现 \(i\) 位是 \(1\)\(i+k\) 位是 \(0\) 的位置,如果不同的位置只有这一种情况,那这个算法均摊下来是对的,但问题是还会有另一种情况是 \(i\) 位是 \(0\)\(i+k\) 位是 \(1\) 当我们发现即使是这样复杂度也是对的,理由如下。

我们将 \(i\)\(i+k\) 连边,我们发现此时会形成若干个环,对于每个环,出现 \(0\) 连向 \(1\)\(1\) 连向 \(0\) 的个数一定相等,所以减少一个 \(0\)\(1\) 就会减少一个 \(1\)\(0\) 。所以均摊下来还是对的。代码是用分治实现的。

需要关注的是这题有十斤 \(corner\space case\) ,例如:

1.如果 \(a_i\) 里有零则会多一种答案为零的情况

2.如果没有一次修改操作,那么只能有一种结果

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010,base=131,mod=1e15+37;
int mp[N],powp[N<<1],fpowp[N<<1],vis[N],p;
il int qpow(int x,int y,int p)
{
	int ret=1;
	while(y)
	{
		if(y&1) ret=(__int128)ret*x%p;
		x=(__int128)x*x%p;y>>=1;
	}
	return ret;
}
il int get_minroot(int x)
{
	vector<int> b;
	int tmp=x-1;
	for(int i=2;i*i<=tmp;i++)
	{
		if(tmp%i==0)
		{
			b.push_back(i);
			while(tmp%i==0) tmp/=i;
		}
	}
	if(tmp!=1) b.push_back(tmp);
	for(int i=1;i<=x;i++)
	{
		int fl=1;
		if(qpow(i,x-1,x)!=1) continue;
		for(int j:b)
		{
			if(qpow(i,(x-1)/j,x)==1)
			{
				fl=0;break;
			}
		}
		if(fl) return i;
	}
	return -1;
}
struct BIT
{
	int s[N<<1];
	il void add(int x)
	{
		x++;
		for(int i=x;i<=2*p;i+=i&-i) s[i]=(s[i]+powp[x])%mod;
	}
	il int query(int x)
	{
		int ret=0;
		for(int i=x;i;i-=i&-i) ret=(ret+s[i])%mod;
		return ret;
	}
	il int query_hash(int l,int r)
	{
		l++,r++;
		return (__int128)(query(r)-query(l-1)+mod)*fpowp[l]%mod;
	}
} B;
multiset<int> st;
vector<int> b;
il void solve(int l,int r,int k)
{
	if(l==r)
	{
		int val1=B.query_hash(l,l),val2=B.query_hash(l+k,l+k);
		if(val2==0 && val1==1)
		{
			if(l+k>=p) b.push_back(l+k-p);
			else b.push_back(l+k);
		}
		return;
	}
	int mid=(l+r)>>1;
	if(B.query_hash(l,mid)!=B.query_hash(l+k,mid+k)) solve(l,mid,k);
	if(B.query_hash(mid+1,r)!=B.query_hash(mid+1+k,r+k)) solve(mid+1,r,k);
}
signed main()
{
	int n;fin>>p>>n;
	int g=get_minroot(p);
	p--;int now=1;
	for(int i=0;i<p;i++)
	{
		mp[now]=i;
		now=now*g%(p+1);
	}
	powp[0]=fpowp[0]=1;int tmp=qpow(base,mod-2,mod);
	for(int i=1;i<=2*p;i++)
	{
		powp[i]=powp[i-1]*base%mod;
		fpowp[i]=(__int128)fpowp[i-1]*tmp%mod;
	}
	int ans=0,fl=0;
	for(int i=1;i<=n;i++)
	{
		int op,w;fin>>op>>w;
		if(op==0) fl=1;
		if(w==0) ans=1;
		else if(op==0)
		{
			if(!vis[w])
			{
				B.add(mp[w]);
				B.add(mp[w]+p);
				vis[w]=1;
			}
		}
		else st.insert(mp[w]);
	}
	if(!fl)
	{
		fout<<p<<"\n";
		return 0;
	}
	for(int i:st)
	{
		b.clear();
		solve(0,p-1,i);
		for(int j:b) B.add(j),B.add(j+p);
	}
	for(int i=0;i<p;i++) ans+=B.query_hash(i,i);
	fout<<p+1-ans<<"\n";
	return 0;
}
/*
原根还是有用的!!
corner case要判全,有零的情形。
通过原根把乘法转成加法
模意义下的0/1可行性背包可以用哈希+树状数组做到均摊双老哥 
*/ 

Fake Plastic Trees 2

首先不难想到一种 \(dp\) 方案是 \(dp_{i,j,k}\) 表示将以 \(i\) 为根的子树分成 \(j\) 个合法连通块,当前包含 \(i\) 的连通块权值和为 \(k\) 的可行性。转移复杂度是 \(O(nkV^2)\) 我们发现这个 \(k\) 这一维还是太吃操作了。考虑把这一维的复杂度压下来。注意到题目只需要求可行性,没让计数,所以只需要保留一些代表元素。

具体的,我们设 \(dp_{i,j}\) 表示该维 \(k\) 所组成的集合 \(S\) ,设现在有 \(x,y,z\in S\) 且满足 \(x<y<z\)\(z-x<=r-l+1\) 由于合法区间的范围是 \(r-l+1\) ,所以若 \(y\) 满足条件,则 \(x\)\(z\) 必有一个也满足条件,所以此时 \(y\) 可以舍去,就做完了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=1010,K=60;
int l,r,a[N],n,k,siz[N];
vector<int> f[N][K],g[K],e[N];
il void DP(int u,int fa)
{
	if(a[u]<=r) f[u][0].push_back(a[u]);
	siz[u]=1;
	for(int v:e[u])
	{
		if(v==fa) continue;
		DP(v,u);
		for(int i=0;i<=k;i++) g[i].clear();
		for(int i=0;i<siz[u] && i<=k;i++)
		{
			for(int j=0;j<siz[v] && i+j<=k;j++)
			{
				for(int a:f[u][i])
				{
					for(int b:f[v][j])
					{
						if(a+b<=r) g[i+j].push_back(a+b);
						if(l<=b && b<=r && i+j<k) g[i+j+1].push_back(a);
					}
				}
			}
		}
		for(int i=0;i<=k;i++)
		{
			sort(g[i].begin(),g[i].end());
			f[u][i].clear();
			for(int x:g[i])
			{
				while((f[u][i].size()>=2 && x-f[u][i][f[u][i].size()-2]<=r-l) || (f[u][i].size() && f[u][i].back()==x)) f[u][i].pop_back();
				f[u][i].push_back(x);
			}
		}
		siz[u]+=siz[v];
	}
}
signed main()
{
	int t;fin>>t;
	while(t--)
	{
		fin>>n>>k>>l>>r;
		for(int i=1;i<=n;i++) e[i].clear();
		for(int i=1;i<=n;i++)
		{
			for(int j=0;j<=k;j++)
			{
				f[i][j].clear();
			}
		}
		for(int i=1;i<=n;i++) fin>>a[i];
		for(int i=1;i<n;i++)
		{
			int u,v;fin>>u>>v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		DP(1,0);
		for(int i=0;i<=k;i++) fout<<(f[1][i].size() && f[1][i].back()>=l);
		fout<<"\n";
	}
	return 0;
}
/*
由于只需要求可行性,所以可以去除大部分无用节点。
留下很少量的有效节点。 
*/

P2305 [NOI2014] 购票

之前在凸包里做过, \(dp\) 式子是简单的,可以用凸包优化,用数据结构维护一下就好。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
#define lid id<<1
#define rid id<<1|1
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=200010,inf=1e18;
int f[18][N],d[N],s[N],p[N],q[N],l[N],son[N],siz[N],top[N],dfn[N],tot,dep[N],dp[N],n,cnt;
vector<int> g[N];
struct node
{
	int x,y;
};
il node operator -(node x,node y){return {x.x-y.x,x.y-y.y};}
il __int128 operator *(node x,node y){return (__int128)x.x*y.y-(__int128)x.y*y.x;}
il bool operator <(node x,node y)
{
	if(x.x!=y.x) return x.y<y.y;
	return x.x<y.x;
}
il void get_son(int u,int fa)
{
	f[0][u]=fa;dep[u]=dep[fa]+1;
	siz[u]=1;
	for(int i=1;i<=17;i++) f[i][u]=f[i-1][f[i-1][u]];
	for(int v:g[u])
	{
		d[v]=d[u]+s[v];
		get_son(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
il void get_top(int u,int t)
{
	top[u]=t;
	dfn[u]=++tot;
	if(son[u]) get_top(son[u],t);
	for(int v:g[u])
	{
		if(v==son[u]) continue;
		get_top(v,v);
	}
}
vector<node> w[N<<2];
bool fl[N<<2];
il void add(int id,int l,int r,int x,node p)
{
	w[id].push_back(p);
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) add(lid,l,mid,x,p);
	else add(rid,mid+1,r,x,p);
}
il void build(int id)
{
	sort(w[id].begin(),w[id].end());
	vector<node> tmp;
	for(node i:w[id]) tmp.push_back(i);
	vector<node> ().swap(w[id]);
	for(node i:tmp)
	{
		int tp=(int)w[id].size()-1;
		while(tp>=1 && (i-w[id][tp])*(w[id][tp]-w[id][tp-1])>=0) tp--,w[id].pop_back();
		w[id].push_back(i);
	}
}
il int get_min(int id,int k)
{
	int l=0,r=(int)w[id].size()-2,ret=w[id].size()-1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if((w[id][mid+1].y-w[id][mid].y)>(__int128)k*(w[id][mid+1].x-w[id][mid].x)) r=mid-1,ret=mid;
		else l=mid+1;
	}
	return w[id][ret].y-k*w[id][ret].x;
}
il int query(int id,int l,int r,int L,int R,int k)
{
	if(L<=l && r<=R)
	{
		if(!fl[id]) fl[id]=1,build(id);
		return get_min(id,k);
	}
	int mid=(l+r)>>1,ret=inf;
	if(L<=mid) ret=min(ret,query(lid,l,mid,L,R,k));
	if(R>mid) ret=min(ret,query(rid,mid+1,r,L,R,k));
	return ret;
}
il void DP(int u)
{
	if(u==1)  add(1,1,n,dfn[u],{0,0});
	else
	{
		int now=u;
		for(int i=17;i>=0;i--)
		{
			if(f[i][now] && d[u]-d[f[i][now]]<=l[u]) now=f[i][now];
		}
		dp[u]=inf;
		int tmp=f[0][u];
		while(top[tmp]!=top[now])
		{
			dp[u]=min(dp[u],query(1,1,n,dfn[top[tmp]],dfn[tmp],p[u])+d[u]*p[u]+q[u]);
			tmp=f[0][top[tmp]];
		}
		dp[u]=min(dp[u],query(1,1,n,dfn[now],dfn[tmp],p[u])+d[u]*p[u]+q[u]);
		add(1,1,n,dfn[u],{d[u],dp[u]});
	}
	for(int v:g[u]) DP(v);
}
signed main()
{
	int t;fin>>n>>t;
	for(int i=2;i<=n;i++)
	{
		int u;fin>>u>>s[i]>>p[i]>>q[i]>>l[i];
		g[u].push_back(i);
	}
	get_son(1,0);get_top(1,1);
	DP(1);
	for(int i=2;i<=n;i++) fout<<dp[i]<<"\n";
	return 0;
}

CF1175G Yet Another Partiton

这种 \(1D/1D\)\(dp\) 如果不能很好的用数据结构维护那么就只能用决策单调性或者 \(cdq\) 分治来做了。而这个题很显然不满足四边形不等式,所以只能用 \(cdq\) 分治来优化了。设当前区间为 \([l,r]\) 分治中心点为 \(mid\) ,设 \(mx_i\) 为当 \(i\le mid\) 时,\(mx_i=max_{j=i+1}^{mid}a_j\),当 \(j>mid\) 时, \(mx_i=max_{j=mid+1}^{i}a_j\) ,那么 \(dp\)\([l,mid]\)\([mid+1,r]\) 转移可以表示为 \(dp_i=min\{dp_j+max(mx_j,mx_{i})\}\) ,那么转移可以分两种情况,一种是 \(mx_j<mx_{i}\) 另一种是 \(mx_j\ge mx_{i}\) 发现两种情况都可以用李超线段树来转移,时间复杂度 \(O(nklog^2n)\),但 \(dzb\) 说好像可以通过神秘李超线段树合并+可持久化李超线段树做到单老哥,先咕了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define lid id<<1
#define rid id<<1|1
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=20010,inf=1e9;
int a[N],g[N],f[N],mx[N],idx[N],n;
vector<int> b;
struct node
{
	int k,b;
	int f(int x){return k*x+b;}
} w[N<<2];
il void add(int id,int l,int r,node x)
{
	if(w[id].k==0 && w[id].b==inf)
	{
		b.push_back(id);
		w[id]=x;
		return;
	}
	int mid=(l+r)>>1;
	if(x.f(mid)<w[id].f(mid)) swap(w[id],x);
	if(x.f(l)<w[id].f(l)) add(lid,l,mid,x);
	if(x.f(r)<w[id].f(r)) add(rid,mid+1,r,x);
}
il int query(int id,int l,int r,int x)
{
	if(l==r) return w[id].f(x);
	int mid=(l+r)>>1,val=w[id].f(x);
	if(x<=mid) return min(val,query(lid,l,mid,x));
	else return min(val,query(rid,mid+1,r,x));
}
il void solve(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	solve(l,mid);
	mx[mid]=0;
	for(int i=mid-1;i>=l;i--) mx[i]=max(mx[i+1],a[i+1]);
	for(int i=mid+1;i<=r;i++) mx[i]=max(mx[i-1],a[i]);
	int i=mid,j=mid+1,k=l;
	while(i>=l && j<=r)
	{
		if(mx[i]<=mx[j]) idx[k++]=i--;
		else idx[k++]=j++;
	}
	while(i>=l) idx[k++]=i--;
	while(j<=r) idx[k++]=j++;
	b.clear();
	for(i=l;i<=r;i++)
	{
		if(idx[i]<=mid) add(1,1,2e4,{-idx[i],g[idx[i]]});
		else f[idx[i]]=min(f[idx[i]],query(1,1,2e4,mx[idx[i]])+idx[i]*mx[idx[i]]);
	}
	for(int i:b) w[i]={0,inf};
	b.clear();
	for(i=r;i>=l;i--)
	{
		if(idx[i]<=mid) add(1,1,n,{mx[idx[i]],g[idx[i]]-idx[i]*mx[idx[i]]});
		else f[idx[i]]=min(f[idx[i]],query(1,1,n,idx[i]));
	}
	for(int i:b) w[i]={0,inf};
	solve(mid+1,r);
}
signed main()
{
	int k;fin>>n>>k;
	for(int i=1;i<=n;i++) fin>>a[i];
	for(int i=1;i<=80000;i++) w[i]={0,inf};
	int mx=0;
	for(int i=1;i<=n;i++)
	{
		mx=max(mx,a[i]);
		f[i]=mx*i;
	}
	for(int i=2;i<=k;i++)
	{
		for(int j=1;j<=n;j++) g[j]=f[j],f[j]=inf;
		solve(1,n);
	}
	fout<<f[n];
	return 0;
}
/*
cdq分治优化dp也是重要思想。
1d/1d类dp可以用cdq/决策单调性两种方式优化 
*/

P9266 [PA 2022] Nawiasowe podziały

这个题和邮局加强版加强版很像,都是 \(wqs\) 二分+决策单调性优化 \(dp\) 状物。唯一难点在于如何类似于莫队地求区间合法括号串,我们发现每个括号串能且仅能和特定的一些位置组成合法括号串,及相邻的括号串,具体实现时我们可以染个色,然后统计有多少颜色相同的相反括号就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=100010,inf=1e18;
int col[N],tot,st[N],tp,n,k,vis[N],f[N],p[N],cnt[N];
string s;
il void get_col()
{
	for(int i=1;i<=n;i++)
	{
		if(s[i]==')')
		{
			if(tp)
			{
				col[i]=col[st[tp]]^1;
				tp--;
				vis[i]=1;
			}
			else
			{
				tot+=2;
				col[i]=tot^1;
			}
		}
		else
		{
			if(i!=1 && s[i-1]==')' && vis[i-1]) col[i]=col[i-1]^1;
			else
			{
				tot+=2;
				col[i]=tot;
			}
			st[++tp]=i;
		}
	}
}
struct node
{
	int now=0,cnt[N<<1],L=1,R=0;
	il int w(int l,int r)
	{
		while(L>l)
		{
			L--;
			if(col[L]%2==0) now+=cnt[col[L]^1];
			cnt[col[L]]++;
		}
		while(R<r)
		{
			R++;
			if(col[R]%2==1) now+=cnt[col[R]^1];
			cnt[col[R]]++;
		}
		while(L<l)
		{
			cnt[col[L]]--;
			if(col[L]%2==0) now-=cnt[col[L]^1];
			L++;
		}
		while(R>r)
		{
			cnt[col[R]]--;
			if(col[R]%2==1) now-=cnt[col[R]^1];
			R--;
		}
		return now;
	}
} A,B;
il void updA(int j,int i,int x)
{
	int val=f[j]+A.w(j+1,i)-x;
	if(val<f[i]) f[i]=val,cnt[i]=cnt[j]+1,p[i]=j;
	else if(val==f[i]) cnt[i]=max(cnt[j]+1,cnt[i]),p[i]=j;
}
il void updB(int j,int i,int x)
{
	int val=f[j]+B.w(j+1,i)-x;
	if(val<f[i]) f[i]=val,cnt[i]=cnt[j]+1,p[i]=j;
	else if(val==f[i]) cnt[i]=max(cnt[j]+1,cnt[i]),p[i]=j;
}
il void solve(int l,int r,int x)
{
	if(r-l<=1) return;
	int mid=(l+r)>>1;
	for(int i=p[l];i<=p[r];i++) updA(i,mid,x);
	solve(l,mid,x);
	for(int i=l+1;i<=mid;i++) updB(i,r,x);
	solve(mid,r,x);
}
il void DP(int x)
{
	for(int i=1;i<=n;i++) p[i]=cnt[i]=0,f[i]=inf;
	for(int i=1;i<=n;i++) updA(0,i,x);
	solve(0,n,x);
}
signed main()
{
	fin>>n>>k;
	fin>>s;s=" "+s;
	get_col();
	int l=-inf,r=0,ret=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		DP(mid);
		if(cnt[n]>=k) r=mid-1,ret=mid;
		else l=mid+1;
	}
	DP(ret);
	fout<<f[n]+k*ret;
	return 0;
}
/*
wqs二分+决策单调性
难点在于如何计算加点、删点的贡献 
*/

P7564 [JOISC 2021] ボディーガード (Day3)

在原数轴的基础上再加一维时间轴,变成一个直角坐标系。那么我们发现此时顾客所走的一定是一条斜率为 \(1\)\(-1\) 线段。而且我们发现保镖也一定存在一种走法,使得他所走的路径是由斜率为 \(1\)\(-1\) 的线段所组成。那么我们可以考虑旋转坐标系,将点 \((x,y)\) 改为点 \((x-y,x+y)\) 即逆时针旋转 \(45^{\circ}\) ,但此时的单位长度变为了原来的两倍,所以只需要将每个 \(C\) 全部都 \(\div 2\) 由于题目保证了 \(C\) 全部都为偶数,所以可以直接除以 \(2\) 。此时顾客一定是向上或向右走的,所以保镖也一定向上或向右走。然后我们可以将我们生成的 \(O(n)\) 个节点延横向和竖向延伸,形成一个为 \(O(n^2)\) 的网格图,其每一条边上都附有一个权值,为经过这条边的权值,其为所有经过这条边的顾客中 \(C\) 最大的值。然后我们可以 \(O(n^2)\space dp\) 求出从每一个格点开始能获得的答案最大值 \(f_{i,j}\)

对于从 \(x,y\) 开始走的保镖,他的行走路径一定是先向上走一段走到格线处,再向右走走到格点处,然后加上此格点的的 \(f\) 值。要么就是先向右走一段走到格线处,再向上走走到格点处,然后加上此格点的的 \(f\) 值。对于第一种情况,能对此保安产生贡献的格点 \((i,j)\) 要满足\(x_{i-1}<x\le x_i,y\le y_i\) ,此贡献值为 \(f_{i,j}+c_{i,j}*(x_i-x)\) 其中 \(c_{i,j}\)\((i,j)\) 左边那条边的权值。容易发现这个东西可以用李超线段树来优化。对于另一种情况也是同理。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define int long long
#define lid id<<1
#define rid id<<1|1
bool St;
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=5610,Q=3000010;
int lshx[N],lshy[N],lsh[Q],cx[N][N],cy[N][N];
long long f[N][N],ans[Q];
struct node
{
	int sx,sy,tx,ty,c;
} w[N];
vector<int> b[N][N];
struct qwq
{
	int x,y;
} q[Q];
struct lichao_tree
{
	struct node
	{
		long long k,b;
		long long f(long long x)
		{
			return k*lsh[x]+b;
		}
	} w[Q<<2];
	il void clear(int n)
	{
		for(int i=1;i<=4*n;i++) w[i]={0,0};
	}
	il void add(int id,int l,int r,node k)
	{
		if(w[id].k==0 && w[id].b==0)
		{
			w[id]=k;
			return;
		}
		int mid=(l+r)>>1;
		if(w[id].f(mid)<k.f(mid)) swap(k,w[id]);
		if(w[id].f(l)<k.f(l)) add(lid,l,mid,k);
		if(w[id].f(r)<k.f(r)) add(rid,mid+1,r,k);
	}
	il long long query(int id,int l,int r,int x)
	{
		if(l==r) return w[id].f(x);
		int mid=(l+r)>>1;
		long long ret=w[id].f(x);
		if(x<=mid) return max(ret,query(lid,l,mid,x));
		else return max(ret,query(rid,mid+1,r,x));
	}
} T;
bool Ed;
signed main()
{
	int n=0,qq=0;fin>>n>>qq;
	for(int i=1;i<=n;i++)
	{
		int t,a,b,c;fin>>t>>a>>b>>c;
		w[i]={t-a,t+a,t+abs(a-b)-b,t+abs(a-b)+b,c/2};
	}
	int totx=0,toty=0;
	for(int i=1;i<=n;i++)
	{
		if(w[i].sx==w[i].tx)
		{
			lshx[++totx]=w[i].sx;
			lshy[++toty]=w[i].sy;
			lshy[++toty]=w[i].ty;
		}
		else
		{
			lshy[++toty]=w[i].sy;
			lshx[++totx]=w[i].sx;
			lshx[++totx]=w[i].tx;
		}
	}
	sort(lshx+1,lshx+1+totx);
	int cntx=unique(lshx+1,lshx+1+totx)-lshx-1;
	for(int i=1;i<=n;i++)
	{
		w[i].sx=lower_bound(lshx+1,lshx+1+cntx,w[i].sx)-lshx;
		w[i].tx=lower_bound(lshx+1,lshx+1+cntx,w[i].tx)-lshx;
	}
	sort(lshy+1,lshy+1+toty);
	int cnty=unique(lshy+1,lshy+1+toty)-lshy-1;
	for(int i=1;i<=n;i++)
	{
		w[i].sy=lower_bound(lshy+1,lshy+1+cnty,w[i].sy)-lshy;
		w[i].ty=lower_bound(lshy+1,lshy+1+cnty,w[i].ty)-lshy;
	}
	for(int i=1;i<=n;i++)
	{
		if(w[i].sx==w[i].tx)
		{
			for(int j=w[i].sy;j<w[i].ty;j++) cy[w[i].sx][j]=max(cy[w[i].sx][j],w[i].c);
		}
		else
		{
			for(int j=w[i].sx;j<w[i].tx;j++) cx[j][w[i].sy]=max(cx[j][w[i].sy],w[i].c);
		}
	}
	for(int i=cntx;i>=1;i--)
	{
		for(int j=cnty;j>=1;j--)
		{
			f[i][j]=max(f[i][j],f[i][j+1]+1ll*cy[i][j]*(lshy[j+1]-lshy[j]));
			f[i][j]=max(f[i][j],f[i+1][j]+1ll*cx[i][j]*(lshx[i+1]-lshx[i]));
		}
	}
	for(int i=1;i<=qq;i++)
	{
		int p,x;fin>>p>>x;
		q[i]={p-x,p+x};
		b[lower_bound(lshx+1,lshx+1+cntx,q[i].x)-lshx][lower_bound(lshy+1,lshy+1+cnty,q[i].y)-lshy].push_back(i);
	}
	for(int i=1;i<=cntx;i++)
	{
		int tot=0;
		for(int j=1;j<=cnty;j++)
		{
			for(int k:b[i][j]) lsh[++tot]=q[k].x;
		}
		if(!tot) continue;
		sort(lsh+1,lsh+1+tot);
		int cnt=unique(lsh+1,lsh+1+tot)-lsh-1; 
		T.clear(cnt);
		for(int j=cnty;j>=1;j--)
		{
			int c=cx[i-1][j];
			T.add(1,1,cnt,{-c,f[i][j]+1ll*c*lshx[i]});
			for(int k:b[i][j])
			{
				int x=lower_bound(lsh+1,lsh+cnt,q[k].x)-lsh;
				ans[k]=max(T.query(1,1,cnt,x),ans[k]);
			}
		}
	}
	for(int i=1;i<=cnty;i++)
	{
		int tot=0;
		for(int j=1;j<=cntx;j++)
		{
			for(int k:b[j][i]) lsh[++tot]=q[k].y;
		}
		if(!tot) continue;
		sort(lsh+1,lsh+1+tot);
		int cnt=unique(lsh+1,lsh+1+tot)-lsh-1; 
		T.clear(cnt);
		for(int j=cntx;j>=1;j--)
		{
			int c=cy[j][i-1];
			T.add(1,1,cnt,{-c,f[j][i]+1ll*c*lshy[i]});
			for(int k:b[j][i])
			{
				int y=lower_bound(lsh+1,lsh+cnt,q[k].y)-lsh;
				ans[k]=max(T.query(1,1,cnt,y),ans[k]);
			}
		}
	}
	for(int i=1;i<=qq;i++) fout<<ans[i]<<"\n";
	return 0;
}
/*
套路叠叠乐
先转坐标系,然后离散成O(n^2)级别的格点,再在上面跑dp
优化可以考虑李超线段树 
*/ 

P5972 [PA 2019] Desant

首先我们有一个朴素的 \(dp\)\(dp_{i,S}\) 表示现在到了第 \(i\) 个元素,前面的元素选的状况是 \(S\),但这样的复杂度很明显爆炸了。考虑优化状态,由于我们只关心每两个元素中有多少个元素,所以我们可以将每两个数之间的元素个数用变进制哈希压成一个状态 \(S\) ,然后做 \(dp\),考虑这样做的正确性,由于我们把一个数拆成若干个数的成绩的最大值一定是拆成若干个 \(3\) 所以状态数不会超过 \(O(3^{\frac{n}{3}})\) ,但问题是暴力递推转移是 \(O(n^23^{\frac{n}{3}})\) 看起来不太能过,所以可以用记忆化搜索优化为 \(O(n3^{\frac{n}{3}})\) 这样就稳过了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=50,inf=1e9;
int a[N],cnt[N][N],rk[N][N],pos[N],tot[N],sum[N],c[N];
vector<pair<int,ll> > f[N];
il void chk_max(pair<int,ll>& x,pair<int,ll> y)
{
	if(y.first<x.first) x=y;
	else if(x.first==y.first) x.second+=y.second;
}
il void dfs(int u,int idx,int rnk,int S,int S0,int S1,int s)
{
	if(u==tot[idx]+1)
	{
		chk_max(f[idx+1][S0],f[idx][S]);
		chk_max(f[idx+1][S1],{f[idx][S].first+s,f[idx][S].second});
		return;
	}
	for(int j=0;j<cnt[idx][u];j++)
	{
		c[u]=j;
		int _S0=S0,_S1=S1,_S=0,_s=s;
		_S=S*cnt[idx][u]+j;
		if(u<rnk || u>rnk+1)
		{
			_S0=S0*cnt[idx][u]+j;
			_S1=S1*cnt[idx][u]+j;
		}
		else if(u==rnk+1)
		{
			_S0=S0*(cnt[idx][u-1]+cnt[idx][u])+c[u]+c[u-1];
			_S1=S1*(cnt[idx][u-1]+cnt[idx][u])+c[u]+c[u-1]+1;
		}
		if(u>rnk) _s+=j;
		dfs(u+1,idx,rnk,_S,_S0,_S1,_s); 
	}
}
signed main()
{
	int n;fin>>n;
	for(int i=1;i<=n;i++) fin>>a[i];
	for(int i=0;i<=n;i++)
	{
		int ttt=0;
		for(int j=i+1;j<=n;j++) pos[++ttt]=a[j];
		sort(pos+1,pos+1+ttt);
		for(int j=1;j<=ttt;j++)
		{
			cnt[i][++tot[i]]=pos[j]-pos[j-1];
			rk[i][pos[j]]=j;
		}
		cnt[i][++tot[i]]=n+1-pos[ttt];
		sum[i]=1;
		for(int j=1;j<=tot[i];j++) sum[i]*=cnt[i][j];
		f[i].resize(sum[i]);
		for(int j=0;j<sum[i];j++) f[i][j]={inf,0};
	}
	f[0][0]={0,1};
	for(int i=1;i<=n;i++) dfs(1,i-1,rk[i-1][a[i]],0,0,0,0);
	for(int i=1;i<=n;i++) fout<<f[n][i].first<<" "<<f[n][i].second<<"\n";
	return 0;
}
/*
变进制哈希,不太好写。
转移用dfs转移,可以省一个n 
*/

Crossing the Border

填坑。首先我们有一个朴素的 \(dp_S\) 表示选了集合 \(S\) 的答案最小值,转移答案时为了不重复计算,我们需要钦定一个顺序,每次选的都是包含 \(c\) 最大值的那一个集合。此时的 \(dp\) 转移就非常好写了。

正解做法,我们考虑折半搜索,设我们现在左边选的集合是 \(L'\) ,右边选的集合是 \(R'\) ,我们从左边选的集合是 \(L\) 右边选的集合是 \(R\) 转移过来,其中 \(L\subseteq L',R\subset R'\) 。我们考虑一个顺序,先把所有不含右边的数的集合转移好,然后剩余每次选的新集合中 \(c\) 的最大值都要是这次选的。我们发现我们可以依次枚举 \(L,R'\)\(L'\) ,对于 \(L'\) 我们可以利用上 \(\sum w\le W\) 的条件,将所有 \(R'\) 的子集按照 \(\sum w\) 从大到小排序,将所有 \(L\) 的超集按照 \(\sum w\) 从大到小排序,这样枚举 \(L'\) 的时候就可以用类似双指针的东西枚举 \(R\) 了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=25,inf=2e9,mod=998244353;
struct node
{
	int w,c;
} a[N];
bool cmp(node x,node y)
{
	return x.c<y.c;
}
int cntl,cntr,dp[1<<11][1<<11],ans[1<<11][1<<11],wl[1<<11],wr[1<<11];
vector<pair<int,int> > l[1<<11],r[1<<11];
void upd(int& x,int& y,int xx,int yy)
{
	if(xx<x) x=xx,y=yy;
	else if(xx==x) y=(y+yy>=mod)?y+yy-mod:y+yy;
}
signed main()
{
	int n,W;fin>>n>>W;cntl=n/2,cntr=n-n/2;
	for(int i=1;i<=n;i++) fin>>a[i].w>>a[i].c;
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<1<<cntl;i++)
	{
		for(int j=1;j<=cntl;j++)
		{
			if(i>>(j-1)&1) wl[i]+=a[j].w;
		}
	}
	for(int i=1;i<1<<cntr;i++)
	{
		for(int j=cntl+1;j<=n;j++)
		{
			if(i>>(j-1-cntl)&1) wr[i]+=a[j].w;
		}
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	ans[0][0]=1;
	for(int s=1;s<1<<cntl;s++)
	{
		for(int t=s;t>=1<<__lg(s);t=(t-1)&s)
		{
			if(wl[t]<=W)
			{
				upd(dp[s][0],ans[s][0],dp[s-t][0]+a[__lg(s)+1].c,ans[s-t][0]);
			}
		}
	}
	for(int s=0;s<1<<cntl;s++)
	{
		for(int t=((1<<cntl)-1)^s;;t=(t-1)&(((1<<cntl)-1)^s))
		{
			int _s=s|t;
			l[s].push_back({wl[_s],_s});
			if(!t) break;
		}
		sort(l[s].begin(),l[s].end());
		reverse(l[s].begin(),l[s].end());
	}
	for(int s=1;s<1<<cntr;s++)
	{
		for(int t=s-(1<<__lg(s));;t=(t-1)&(s-(1<<__lg(s))))
		{
			r[s].push_back({wr[t],t});
			if(!t) break;
		}
		sort(r[s].begin(),r[s].end());
		reverse(r[s].begin(),r[s].end());
	}
	for(int _R=1;_R<(1<<cntr);_R++)
	{
		for(int L=0;L<(1<<cntl);L++)
		{
			int now=0,mn=inf,www=0;
			for(auto x:l[L])
			{
				int _L=x.second;
				while(now<(int)r[_R].size() && wl[_L]-wl[L]+wr[_R]-wr[r[_R][now].second]<=W)
				{
					int R=r[_R][now].second;
					upd(mn,www,dp[L][R]+a[__lg(_R)+1+cntl].c,ans[L][R]);
					now++;
				}
				upd(dp[_L][_R],ans[_L][_R],mn,www);
			}
		}
	}
	fout<<dp[(1<<cntl)-1][(1<<cntr)-1]<<" "<<ans[(1<<cntl)-1][(1<<cntr)-1]<<"\n";
	return 0;
}

Mahjong

看到麻将第一反应就应该是 \(dp\)\(dp\) ,内层 \(dp_{i,n_1,n_2,p}\) 表示 \(i-1\) 开始的顺子个数为 \(n_1\)\(i\) 开始的顺子个数为 \(n_2\) ,现在有没有对子。 \(p\in[0,1]\) 是显然的,我们现在强制钦定 \(n_1,n_2\in [0,2]\) ,因为如果同一种顺子超过 \(2\) 那我们一定可以将其拆出三个面子,这样剩余的相同的顺子就小于等于 \(2\) 。转移就是先满足 \(n_1\)\(n_2\) 两个顺子,然后能组对子组队子,能组面子组面子,剩下的全塞给从 \(i\) 开始的顺子。当我们发现这样的话内层 \(dp\) 状态数过多了,达到了 \(2^{3\times 3\times 2}=2^{18}\) ,当我们发现实际能到达的非常少,所以可以直接留下有效位做转移,一共是 \(68\) 种,外层 \(dp\) 随便套一个 \(dp_{i,j,s}\) 表示现在选到第 \(i\) 种牌,选了 \(j\) 个牌,到了自动机上第 \(s\) 个节点。最后就是统计所有内层 \(dp\) 满足 \(f_{0,0,1}\) 等于 \(1\) 即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define il inline
struct IO
{
	static const int Size=(1<<21);
	char buf[Size],*p1,*p2;
	int st[105],Top;
	~IO(){clear();}
	il void clear(){fwrite(buf,1,Top,stdout);Top=0;}
	il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2)?EOF:*p1++;}
	il void pc(const char c){Top==Size&&(clear(),0);buf[Top++]=c;}
	il IO& operator >>(char& c){while(c=gc(),c==' ' || c=='\n' || c=='\r');return *this;}
	template<typename T>il IO& operator >>(T& x)
	{
		x=0;bool f=0;char c=gc();
		while(!isdigit(c)){if(c=='-') f=1;c=gc();}
		while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
		f?x=-x:0;
		return *this;
	}
	il IO& operator >>(string& s)
	{
		s="";char c=gc();
		while(c==' ' || c=='\n' || c=='\r') c=gc();
		while(c!=' ' && c!='\n' && c!='\r' && c!=EOF) s+=c,c=gc();
		return *this;
	}
	il IO& operator <<(const char c){pc(c);return *this;}
	template<typename T> il IO& operator <<(T x)
	{
		if(x<0) pc('-'),x=-x;
		do st[++st[0]]=x%10,x/=10;while(x);
		while(st[0]) pc(st[st[0]--]+'0');
		return *this;
	}
	il IO& operator <<(const string s){for(auto c:s) pc(c);return *this;}
	il IO& operator <<(const char* c){for(int i=0;c[i];i++) pc(c[i]);return *this;}
} fin,fout;
const int N=210,mod=1e9+7;
int g[1<<18][5],now[3][3][2],vis[1<<18],you[70],dp[N][N][70],to[70][5];
int add(int S,int x)
{
	memset(now,0,sizeof(now));
	int tmp=0;
	for(int i=0;i<=2;i++)
	{
		for(int j=0;j<=2;j++)
		{
			for(int k=0;k<=1;k++)
			{
				int pre=S>>tmp&1,w=x;
				tmp++;
				if(!pre || w<i+j) continue;
				w-=i+j;
				int _i=j,_j=0,_k=k;
				if(!k && w>=2)
				{
					_k=1,w-=2;_j=w;
					now[_i][_j][_k]=1;
					_k=0,w+=2;
				}
				if(w>=3)
				{
					w-=3;_j=w;
					now[_i][_j][_k]=1;
					w+=3;
				}
				if(w<=2)
				{
					_j=w;
					now[_i][_j][_k]=1;
				}
			}
		}
	}
	tmp=0;int ret=0;
	for(int i=0;i<=2;i++)
	{
		for(int j=0;j<=2;j++)
		{
			for(int k=0;k<=1;k++)
			{
				ret|=now[i][j][k]<<tmp;
				tmp++;
			}
		}
	}
	return ret;
}
il void upd(int& x,int y)
{
	x=(x+y>=mod)?x+y-mod:x+y;
}
signed main()
{
	for(int i=0;i<1<<18;i++)
	{
		for(int j=0;j<=4;j++)
		{
			g[i][j]=add(i,j);
		}
	}
	queue<int> q;q.push(1);vis[1]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=0;i<=4;i++)
		{
			int v=g[u][i];
			if(!vis[v])
			{
				vis[v]=1;
				q.push(v);
			}
		}
	}
	int cnt=0;
	for(int i=0;i<1<<18;i++)
	{
		if(vis[i]) you[++cnt]=i;
	}
	for(int i=0;i<1<<18;i++)
	{
		if(vis[i])
		{
			int idx=lower_bound(you+1,you+1+cnt,i)-you;
			for(int j=0;j<=4;j++)
			{
				to[idx][j]=lower_bound(you+1,you+1+cnt,g[i][j])-you;
			}
		}
	}
	int T;fin>>T;
	for(int u=1;u<=T;u++)
	{
		int n,m;fin>>n>>m;
		memset(dp,0,sizeof(dp));
		dp[0][0][2]=1;
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<=m;j++)
			{
				for(int S=1;S<=cnt;S++)
				{
					if(!dp[i][j][S]) continue;
					for(int k=0;k<=4 && j+k<=m;k++)
					{
						upd(dp[i+1][j+k][to[S][k]],dp[i][j][S]);
					}
				}
			}
		}
		int ans=0;
		for(int i=1;i<=cnt;i++)
		{
			if(you[i]>>1&1) upd(ans,dp[n][m][i]);
		}
		fout<<"Case #"<<u<<": "<<ans<<"\n";
	}
	return 0;
}
posted @ 2026-05-12 18:21  stuart220_chen  阅读(19)  评论(0)    收藏  举报