dp杂题

都是讲课的例题,挑了一些可做题做。

文章目录

  • luoguP2605 [ZJOI2010]基站选址
  • bzoj3572: [Hnoi2014]世界树
  • bzoj4543: [POI2014]Hotel加强版
  • uoj462. 新年的小黄鸭
  • uoj22.【UR #1】外星人
  • uoj141. 【UER #4】量子态的棋盘

luoguP2605 [ZJOI2010]基站选址

どこでもドア
很有年代的题了,感觉刚学OI没多久就听人讲过,现在应该已经是大水题了吧。
f [ i ] [ j ] f[i][j] f[i][j]表示前i个点建了j个基站,第j个在i号点的方案数。
用线段树优化转移,找到一个最小的 f [ i ′ ] [ j − 1 ] f[i'][j-1] f[i][j1]转移过来。 i ′ i' i~ i i i中间有部分没有被 i i i i ′ i' i覆盖的村庄会有额外代价,每到一个 i i i把它不能覆盖到的那些点拿出来,对不能覆盖到它的 i ’ i’ i区间加。
我们需要k棵线段树,空间炸了,于是先枚举j然后滚动线段树。
为了方便在前后各加一个极远点规定必须放基站。
一开始线段树写挂了T了一个点,luogu评测的同时编译器帮我把bug找出来了,太良心了吧。

//Achen
#include
#define For(i,a,b) for(register int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(register int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=20007,up=2e9+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,k,d[N],c[N],s[N],w[N];
int f[N][107];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

#define inf 1e9
#define lc (x<<1)
#define rc ((x<<1)|1)
#define mid ((l+r)>>1)
struct sgtree {
	int sg[N<<2],lz[N<<2];
	void build(int x,int l,int r) {
		sg[x]=lz[x]=0;
		if(l==r) { sg[x]=inf; return; }
		build(lc,l,mid); build(rc,mid+1,r);
		sg[x]=min(sg[lc],sg[rc]);
	}
	
	inline void change(int x,int l,int r,int pos,int v) {
		if(l==r) { sg[x]=v; return; }
		if(pos<=mid) change(lc,l,mid,pos,v);
		else change(rc,mid+1,r,pos,v);
		sg[x]=min(sg[lc],sg[rc])+lz[x];
	}
	
	inline void upd(int x,int l,int r,int ql,int qr,int v) {
		if(l==ql&&r==qr) { sg[x]+=v; lz[x]+=v; return; }
		if(qr<=mid) upd(lc,l,mid,ql,qr,v);
		else if(ql>mid) upd(rc,mid+1,r,ql,qr,v);	
		else {
			upd(lc,l,mid,ql,mid,v); 
			upd(rc,mid+1,r,mid+1,qr,v);
		} sg[x]=min(sg[lc],sg[rc])+lz[x];
	}
	
	inline int qry(int x,int l,int r,int ql,int qr) {
		if(l==ql&&r==qr) return sg[x];
		if(qr<=mid) return qry(lc,l,mid,ql,qr)+lz[x];
		if(ql>mid) return qry(rc,mid+1,r,ql,qr)+lz[x];	
		return min(qry(lc,l,mid,ql,mid),qry(rc,mid+1,r,mid+1,qr))+lz[x];
	}
}T[2];
struct node { 
	int x,ds;
	friend bool operator <(const node&A,const node&B) {
		return A.ds<B.ds||(A.ds==B.ds&&A.x<B.x);
	}
};set<node>S,S2;
#define IT set::iterator 

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n); read(k);
    For(i,3,n+1) read(d[i]);
	For(i,2,n+1) read(c[i]);
	For(i,2,n+1) read(s[i]);
	For(i,2,n+1) read(w[i]); 
	n+=2; k+=2;  
	d[1]=-up; d[n]=up;
	memset(f,127/3,sizeof(f));
	int o=0;
//	For(l,1,k) 
	T[o].build(1,1,n); 
	f[1][1]=0;
	T[o].change(1,1,n,1,0);
	For(j,2,k) {
		o^=1; T[o].build(1,1,n); 
		while(!S.empty()) S.erase(S.begin());
		For(i,1,n) {
			S.insert((node){i,d[i]+s[i]});
			S2.insert((node){i,d[i]});
		}
		For(i,2,n) {
			while(!S.empty()) {
				node t=*S.begin();
				if(t.ds<d[i]) {
					S.erase(S.begin());
					IT it=S2.lower_bound((node){0,d[t.x]-s[t.x]});
					if(it!=S2.begin()) {
						int pos=(*it).x-1;
						T[o^1].upd(1,1,n,1,pos,w[t.x]);
					}
				}
				else break;
			}
			f[i][j]=min((int)inf,T[o^1].qry(1,1,n,1,i-1)+c[i]); 
			if(f[i][j]!=inf) T[o].change(1,1,n,i,f[i][j]);
		}
	}
	int ans=f[n][2];
	For(i,2,k) ans=min(ans,f[n][i]);
	printf("%d\n",ans);
	Formylove;
}

bzoj3572: [Hnoi2014]世界树

どこでもドア
虚树小水题。然鹅我又思维僵化,做了两道树dp预留一部分到lca处理的然后就一直在想怎么处理。
实际上暴力dp把虚树上的点最近的管辖点找出来,然后虚树上两点之间的部分二分一个中间点分别归两端点的管辖点管就好惹。。。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=300007;
typedef long long LL;
typedef double db;
using namespace std;
int n,q,ans[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N<<1],to[N<<1];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}

int f[N][20],R[N],sz[N],dfn[N],dfk;
void dfs1(int x,int fa) {
	sz[x]=1;
	f[x][0]=fa;
	R[x]=R[fa]+1;
	dfn[x]=++dfk;
	For(i,1,18) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=fir[x];i;i=nxt[i]) if(to[i]!=fa) {
		dfs1(to[i],x);
		sz[x]+=sz[to[i]];
	}
}

int in(int a,int b) { return dfn[a]>=dfn[b]&&dfn[a]<dfn[b]+sz[b]; }

int lca(int x,int y) {
	if(R[x]<R[y]) swap(x,y);
	Rep(i,18,0) if(R[f[x][i]]>=R[y])
		x=f[x][i];
	if(x==y) return x;
	Rep(i,18,0) if(f[x][i]!=f[y][i])
		x=f[x][i],y=f[y][i];
	return f[x][0];
}

int get_x(int x,int y) { Rep(i,18,0) if(R[f[y][i]]>R[x]) y=f[y][i]; return y; }

int dis(int x,int y) { return R[x]+R[y]-2*R[lca(x,y)]; }

bool cmp(const int &A,const int &B) { return dfn[A]<dfn[B]; }

int m,tot,a[N],is[N],sta[N],top,nr[N],nrd[N];
vector<int>vc[N];

int nsz[N],psz[N];
void dfs2(int x) {
	nr[x]=is[x]?x:0;
	nsz[x]=sz[x];
	int up=vc[x].size();
	For(i,0,up-1) {
		int y=vc[x][i],z=get_x(x,y);
		nsz[x]-=sz[z]; 
		psz[y]=sz[z]-sz[y];
		dfs2(y);
		if(!nr[x]||(nr[y]&&(R[nr[y]]<R[nr[x]]||(R[nr[y]]==R[nr[x]]&&nr[y]<nr[x])))) nr[x]=nr[y];		
	} 
	nrd[x]=R[nr[x]]-R[x];
}

void dfs3(int x) {
	int up=vc[x].size();
	ans[is[nr[x]]]+=nsz[x];
	For(i,0,up-1) {
		int y=vc[x][i],dd=R[y]-R[x];
		if(!nr[y]||(nr[x]&&(nrd[x]+dd<nrd[y]||(nrd[x]+dd==nrd[y]&&nr[x]<nr[y])))) {
			nr[y]=nr[x];
			nrd[y]=nrd[x]+dd;
		}
		if(nr[x]==nr[y]) ans[is[nr[x]]]+=psz[y];
		else {
			int z=y;
			Rep(i,18,0) if(R[f[z][i]]>R[x]) {
				int d1=dis(f[z][i],nr[x]),d2=dis(f[z][i],nr[y]);
				if(d1>d2||(d1==d2&&nr[x]>nr[y])) z=f[z][i];
			}
			if(psz[y]-(sz[z]-sz[y])<0) {
				int debug=1;
			}
			ans[is[nr[y]]]+=sz[z]-sz[y];
			ans[is[nr[x]]]+=psz[y]-(sz[z]-sz[y]);
		}
		dfs3(y);
	}
}

void solve() {
	while(top) top--;
	For(i,1,tot) is[a[i]]=0,vc[a[i]].clear();
	read(m); tot=m;
	For(i,1,m) {
		read(a[i]); 
		is[a[i]]=i;
		ans[i]=0;
	}
	sort(a+1,a+m+1,cmp); 
	a[++tot]=1;
	For(i,1,m-1) a[++tot]=lca(a[i],a[i+1]); 
	sort(a+1,a+tot+1,cmp);
	tot=unique(a+1,a+tot+1)-(a+1);
	For(i,1,tot) {
		while(top&&!in(a[i],sta[top])) top--;
		if(top&&in(a[i],sta[top])) vc[sta[top]].push_back(a[i]);
		sta[++top]=a[i];
	}
	dfs2(1); dfs3(1);
	For(i,1,m-1) printf("%d ",ans[i]); 
	printf("%d\n",ans[m]);
}

int main() {
    //freopen("3572.in","r",stdin);
    //freopen("3572.out","w",stdout);
    read(n); 
    For(i,2,n) {
    	int u,v;
    	read(u); read(v);
    	add(u,v);
	}
	dfs1(1,0);
	read(q);
	For(i,1,q) solve();
	Formylove;
}

bzoj4543: [POI2014]Hotel加强版

どこでもドア
这题题解很多,都写得蛮清楚的。
三点形成倒Y型,对每个倒Y型找到下面两个点后预留出上面部分,由此列出dp方程。发现第一次转移很特殊,可以用儿子的数组进行移动得到。长链剖分后,用指针直接继承重儿子的dp数组,然后轻儿子的暴力合并。因为每条轻链仅被暴力合并一次,复杂度为O(n).

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=100007;
const int M=1000007;
typedef long long LL; 
typedef double db;
using namespace std;
int n;
LL nonam[M],ans;
LL *f[N],*g[N],*np=nonam;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int ecnt,fir[N],nxt[N<<1],to[N<<1];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
}

int dep[N],mson[N];
void dfs1(int x,int fa) {
	for(int i=fir[x];i;i=nxt[i]) if(to[i]!=fa) {
		dfs1(to[i],x);
		dep[x]=max(dep[x],dep[to[i]]+1);
		if(dep[x]==dep[to[i]]+1) mson[x]=to[i];
	}
}

void givesp(int x) {
	f[x]=np; np+=dep[x]*2+2;
	g[x]=np+dep[x]+1; np+=dep[x]*2+2;
}

void dfs2(int x,int fa) {
	if(mson[x]) {
		f[mson[x]]=f[x]+1; g[mson[x]]=g[x]-1;
		dfs2(mson[x],x);
	}
	f[x][0]=1; ans+=g[x][0];
	for(int i=fir[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa||y==mson[x]) continue;
		givesp(y); dfs2(y,x);
		For(j,0,dep[y]) ans+=(j?g[y][j]*f[x][j-1]:0)+f[y][j]*g[x][j+1];
		For(j,0,dep[y]) {
			g[x][j+1]+=f[x][j+1]*f[y][j];
			if(j) g[x][j-1]+=g[y][j];
		}
		For(j,0,dep[y])	f[x][j+1]+=f[y][j];		
	}
}

int main() {
	//freopen("4543.in","r",stdin);
	//freopen("4543.out","w",stdout);
    read(n);
    For(i,2,n) {
    	int x,y;
    	read(x); read(y);
    	add(x,y);
    }
    dfs1(1,0); 
    givesp(1); dfs2(1,0);
    printf("%lld\n",ans);
    return 0;
}

uoj462. 新年的小黄鸭

どこでもドア
抄了一位dalao的代码,然后根据代码不负责口胡。
f x , i f_{x,i} fx,i表示 x x x点向上重链长为 i i i的子树内最小代价。(z是x的儿子)
f x , i = m i n ( f y , i − 1 + ∑ f z , 0 − f y , 0 + ⌈ l o g ( i ) ⌉ ∗ ( s z x − s z y ) ) f_{x,i}=min( f_{y,i-1}+\sum f_{z,0}-f_{y,0}+\lceil log(i) \rceil *(sz_x-sz_y)) fx,i=min(fy,i1+fz,0fy,0+log(i)(szxszy))
把只有关 x x x的移出来,有关 y y y的留在 m i n min min中,就可以在枚举 i i i的情况下用数据结构找到合适的 y y y来转移。
然鹅这样是 n 2 n^2 n2的,遗憾地向我们表明不资瓷枚举 i i i。如果不用枚举 i i i,而可以用数据结构优化转移,岂不美滋滋。
不记录每个人向上的重链长又想知道重链的代价,这次我们考虑用f[x]表示x是一条重链头的代价,然后转移直接给x找一条完整的重链出来。枚举x所在重链尾,只需要枚举x子树中的叶子就好了,然后我们试图把每种转移的代价放到每个重链尾上计算。

以我微薄的语言功底不足以描述算法的精妙(下面我不知道该怎么写了) ,只可意会不可言传。(溜了溜了

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Tra(i,x,y) for(int i=fir[x],y=to[i];i;i=nxt[i],y=to[i]) 
const int N=1e5+7; 
using namespace std;
typedef long long LL;
typedef double db;
int n,dp[N]; 

template<typename T>void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int sg[N<<2],lz[N<<2];
#define lc (x<<1)
#define rc ((x<<1)|1)
#define mid ((l+r)>>1)
void add(int x,int l,int r,int ql,int qr,int v) {
	if(l>=ql&&r<=qr) {
		sg[x]+=v; lz[x]+=v; return ;
	}
	if(ql<=mid) add(lc,l,mid,ql,qr,v);
	if(qr>mid) add(rc,mid+1,r,ql,qr,v);
	sg[x]=min(sg[lc],sg[rc])+lz[x];
}

int qry(int x,int l,int r,int ql,int qr) {
	if(l>=ql&&r<=qr) return sg[x];
	if(qr<=mid) return qry(lc,l,mid,ql,qr)+lz[x];
	if(ql>mid) return qry(rc,mid+1,r,ql,qr)+lz[x];
	return min(qry(lc,l,mid,ql,qr),qry(rc,mid+1,r,ql,qr))+lz[x];
}

int ecnt,fir[N],nxt[N<<1],to[N<<1];
void add(int u,int v) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u;
} 

int f[N][17],sz[N],lf[N],rf[N],tot;
vector<int>vc[N];
void dfs1(int x,int fa) {
	sz[x]=1;
	f[x][0]=fa;
	lf[x]=tot+1;
	if(fa) vc[fa].push_back(x); 
	For(i,0,16) {
		if(i) f[x][i]=f[f[x][i-1]][i-1];
		if(f[f[x][i]][0]) vc[f[f[x][i]][0]].push_back(x);
	}
	Tra(i,x,y) if(y^fa) {
		dfs1(y,x);
		sz[x]+=sz[y];
	}
	if(sz[x]==1&&fa) tot++;
	rf[x]=tot;
}

void dfs2(int x,int fa) {
	int sum=0;
	Tra(i,x,y) if(y^fa) {
		dfs2(y,x);
		sum+=dp[y]+sz[y];
	}
	Tra(i,x,y) if(y^fa) 
		add(1,1,tot,lf[y],rf[y],sum-dp[y]-sz[y]);
	int up=vc[x].size();
	For(i,0,up-1) {
		int y=vc[x][i];
		add(1,1,tot,lf[y],rf[y],sz[y]);
	}
	dp[x]=qry(1,1,tot,lf[x],rf[x]);
	For(i,0,up-1) {
		int y=vc[x][i];
		add(1,1,tot,lf[y],rf[y],-sz[y]);
	}	
}

int main() {
	//freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n);
	For(i,2,n) {
		int x,y;
		read(x); read(y);
		add(x,y); 
	}
	dfs1(1,0);
	dfs2(1,0);
	printf("%d\n",dp[1]);
    return 0;
}

uoj22.【UR #1】外星人

どこでもドア
定义有效的传递为使x改变的传递,一个排列中有效的传递的a一定是递减的。
把a数组从大到小排序,然后考虑每个a在排列中的第几个出现。设 f [ i ] [ j ] f[i][j] f[i][j]表示前i大的数都已经填入排列,现在的x为j的方案数。
当前考虑到第i大的a,它要么出现在目前排列的最前一个空位置,然后直接无脑转移到 f [ i + 1 ] [ j   m o d   a i ] f[i+1][j\ mod\ a_i] f[i+1][j mod ai]。如果它不是出现在目前排列的最前一个空位置,说明有一个比它小的数排在它前面,它一定是一个无效转移,它出现在任何位置最终的x都不会改变,组合数转移到 f [ i + 1 ] [ j ] f[i+1][j] f[i+1][j]即可。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0 
const int N=5007,p=998244353; 
using namespace std;
typedef long long LL;
typedef double db;
int n,x,a[N],f[1007][N],ok[1007][N]; 

template<typename T>void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

bool cmp(const int &A,const int &B) { return A>B; }

int main() {
	//freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n); read(x);
    For(i,1,n) read(a[i]);
    sort(a+1,a+n+1,cmp);
    f[0][x]=1;
    ok[0][x]=1;
	For(i,1,n) {
		For(j,0,x) if(f[i-1][j]) {
			(f[i][j%a[i]]+=f[i-1][j])%=p;
			if(ok[i-1][j]) ok[i][j%a[i]]=1;
			if(n-i) {
				(f[i][j]+=(LL)f[i-1][j]*(n-i)%p)%=p;
				if(ok[i-1][j]) ok[i][j]=1;
			}
		}
	}
	Rep(i,x,0) if(ok[n][i]) {
		printf("%d\n%d\n",i,f[n][i]);
		Formylove;
	}
}

uoj141. 【UER #4】量子态的棋盘

どこでもドア
比赛里面有详细的题解。
经过一个格子的 x x x个小球,一定有 ⌊ x 2 ⌋ \lfloor\frac{x}{2}\rfloor 2x滚向右边, ⌊ x 2 ⌋ \lfloor\frac{x}{2}\rfloor 2x个滚向下面,还有一个根据它上面的数字滚向右边或下面。也就是说每个格子上最多会有一个球不知去处,而其它球的去处是一定的, k ≤ 1 0 18 k\leq10^{18} k1018是吓人的,一次dp就可以求出所有去处一定的小球的去处
f 1 , 1 = k f i , j = ⌊ f i − 1 , j 2 ⌋ + ⌊ f i , j − 1 2 ⌋ f_{1,1}=k \\f_{i,j}=\lfloor \frac{f_{i-1,j}}{2} \rfloor+\lfloor \frac{f_{i,j-1}}{2} \rfloor f1,1=kfi,j=2fi1,j+2fi,j1
dp后f为奇数的点就是还有小球停留的点,现在只需要考虑这最多n*m个小球即可。
考虑轮廓线dp,状压每条轮廓线上正落向这个方向的小球个数(不超过 n m 2 \frac{nm}{2} 2nm,可以51进制状压),和当前状态已经落入洞中的小球个数。看起来非常不科学然鹅出题人说有效状态很少。瞎那啥hash一下状态瞎那啥转移一下就过了,复杂度 O ( O( O(能过 ) ) )

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=3e5+7,mod=299977,pp=998244353;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,b[15][15];
LL K,Q,a[15][15],bsans,pr[15],sum[107];
char s1[15],s2[15];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct zt {
	int tot; LL s;
};

struct hash {
	int tot;
	vector<int>vc[N];
	zt tid[N];	
	int get_hash(zt A) { return (A.s%mod*131+A.tot)%mod; }
	int ins(zt A) { 
		int hs=get_hash(A),up=vc[hs].size();
		For(i,0,up-1) {
			int id=vc[hs][i];
			if(tid[id].s==A.s&&tid[id].tot==A.tot) return id;
		}
		tid[++tot]=A; vc[hs].push_back(tot); return tot;
	}
	void del(zt A) {
		int hs=get_hash(A); vc[hs].clear();
	}
}H[2];

int get_lkx(LL s,int i) { return s%pr[i]/pr[i-1]; }

LL f[2][N];
void solve() {
	pr[0]=1;
	For(i,1,m+1) pr[i]=pr[i-1]*51;
	int o=0; 
	int x=H[o].ins((zt){0,0});
	f[o][x]=1;
	For(i,1,n) For(j,1,m) {
		o^=1;
		int up=H[o^1].tot;
		For(s,1,up) {
			zt S=H[o^1].tid[s],nx; H[o^1].del(S);
			
			if(j==1) S.s*=51;
			
			int c1=get_lkx(S.s,j),c2=get_lkx(S.s,j+1);
			int c=c1+c2+b[i][j];
 
			nx.s=S.s+(c/2-c1)*pr[j-1]+((c+1)/2-c2)*pr[j];
			nx.tot=S.tot;
			if(j==m) { 
				int cc=get_lkx(nx.s,j+1); 
				nx.s-=cc*pr[j]; if(s1[i-1]=='1') nx.tot+=cc;
			} 
			if(i==n) {  
				int cc=get_lkx(nx.s,j); 
				nx.s-=cc*pr[j-1]; if(s2[j-1]=='1') nx.tot+=cc;
			}
			int y=H[o].ins(nx); 
			(f[o][y]+=f[o^1][s])%=pp;
			
			nx.s=S.s+((c+1)/2-c1)*pr[j-1]+(c/2-c2)*pr[j];
			nx.tot=S.tot;
			if(j==m) { 
				int cc=get_lkx(nx.s,j+1); 
				nx.s-=cc*pr[j]; if(s1[i-1]=='1') nx.tot+=cc;
			} 
			if(i==n) {  
				int cc=get_lkx(nx.s,j); 
				nx.s-=cc*pr[j-1]; if(s2[j-1]=='1') nx.tot+=cc;
			}
			y=H[o].ins(nx); 
			(f[o][y]+=f[o^1][s])%=pp;
		}
		For(l,1,H[o^1].tot) f[o^1][l]=0; H[o^1].tot=0;
	}
	For(i,1,H[o].tot) {
		(sum[(H[o].tid[i]).tot]+=f[o][i])%=pp;
	}
	//For(i,0,n*m) printf("%lld ",sum[i]);
}

int main() {
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n); read(m); read(K);
    scanf("%s%s",s1,s2);
	For(i,1,n+1) For(j,1,m+1) {
		if(i==1&&j==1) a[i][j]=K;
		else {
			if(j!=m+1) a[i][j]+=a[i-1][j]/2;
			if(i!=n+1) a[i][j]+=a[i][j-1]/2;
		}
		b[i][j]=(a[i][j]&1);
	}
	For(i,1,n) if(s1[i-1]=='1') bsans+=a[i][m+1];
	For(i,1,m) if(s2[i-1]=='1') bsans+=a[n+1][i];
	solve();
	read(Q);
	For(i,1,Q) {
		LL ql,qr,rs=0;
		read(ql); read(qr);
		ql-=bsans; qr-=bsans;
		for(LL j=max(0LL,ql);j<=min((LL)n*m,qr);j++) {
			assert(j>=0&&j<=n*m);
			(rs+=sum[j])%=pp;
		}
		printf("%lld\n",rs); 
	}
	Formylove;
}

你可能感兴趣的:(动态规划)