总传送门

T1. [USACO19JAN] Redistricting P

luogu P5202

  • 思路:
    这种每次选出段长有个上限k的常常是和单调队列有关。
    这里是单调队列优化dp
    不过一开始想不太清有什么单调性。
    发现每次的贡献为0/1
    因此如果i<jdpi<dpji最多就和j一样贡献直接删去。
    如果dpi=dpj你需要考虑中间段的贡献,决定是否删。
    不过总之我们的目的是让优劣性单调递减(队首最优)
    如果段[i+1..j]cnt(H)>cnt(G)i能取到时一定会比j优,所以保留i
    否则直接弹出i
  • code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
char s[N];
int dp[N],sh[N],sg[N];
int Q[N],hd=1,tl;
bool W(int l,int r) {
	return (sg[r]-sg[l]>=sh[r]-sh[l]);
}
bool cmp(int x,int y) {return dp[x]==dp[y]?W(x,y):dp[x]>dp[y];}
int main() {
	int n,k,l=1,r;
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)sh[i]=sh[i-1]+(s[i]=='H'),sg[i]=sg[i-1]+(s[i]=='G');
	dp[0]=0;Q[++tl]=0;
	for(int i=1;i<=n;i++) {
		while(hd<=tl&&Q[hd]+k<i) hd++;
		dp[i]=dp[Q[hd]]+W(Q[hd],i);
		while(hd<=tl&&cmp(Q[tl],i)) tl--;
		Q[++tl]=i;
	}
	printf("%d\n",dp[n]);
	return 0;
}

T2.[USACO20JAN] Cave Paintings P

luogu P6008

  • 思路:
    并查集
    最简单的思路就是从下往上灌水。
    如果某两个节点在i+1行不连通,在第i行联通。然后这两个节点在in时是独立的。(如果独立就相当于乘法原理,不独立就会合成同类一起作贡献)
    推广到带权并查集,合并两个联通块价值为它们乘积+1
    当然我考场上想复杂了。
    因为我一直往图论上套(思维僵化,犯了跟noionline一样的错误)。
    不过还是搞了很久,我把它缩点(如果两个点通过往下、左、右走能联通就合成一个点一起作贡献)
    可能因为我这种做法每行横向一段是手动缩点(而不是并查集),就跑的飞快。
    缩点后成为森林,树上dp。(别忘了记录完起点)
    具体缩点写法,(和正常写法很像)到能合并的时机时,加一个新点代表(合并后的点),连向所有每个都能到的下层的节点(下层的森林上的节点已经构造好了)。
  • code:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5;
const int M=2e6+5;
const int mod=1e9+7;
int n,m,st[M],tp;
char s[N][N];
int tt[N],ct,Ll,Rl,fa[M],nxt[M],to[M],head[M],ecnt,nd,pos[N][N];
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
int gt_fa(int u) {return fa[u]==u?u:fa[u]=gt_fa(fa[u]);}
void Union(int u,int v) {fa[gt_fa(u)]=gt_fa(v);}
int rt[M];
void Build() {
	bool bg=0;
	for(int i=n;i;i--) {
//		printf("!%d\n",i);
		if(!bg) {
			for(int j=1;j<=m;j++) {
				if(s[i][j]=='.') {
					++nd;fa[nd]=nd;pos[i][j]=nd;
					while(s[i][j+1]=='.') {
						j++;pos[i][j]=nd;
					}
				}
			}
			if(nd){Ll=1;Rl=nd;bg=1;}
			continue;
		}
		for(int j=1;j<=m;j++) {
			if(s[i][j]=='.') {
				++nd;fa[nd]=nd;pos[i][j]=nd;		//tmp node
				if(pos[i+1][j])Union(nd,pos[i+1][j]);
				while(s[i][j+1]=='.') {
					j++;pos[i][j]=nd;if(pos[i+1][j])Union(nd,pos[i+1][j]);
				}
			}
		}
//		new node
		int nwR=nd;
		ct=0;
		for(int j=1;j<=m;j++) {
			if(s[i][j]=='#') continue;
			int x=gt_fa(pos[i][j]);
			if(!rt[x]) {rt[x]=++nd;tt[++ct]=x;fa[nd]=nd;}
			pos[i][j]=rt[x];
		}
		for(int j=Ll;j<=Rl;j++) {
			int x=gt_fa(j);
			if(!rt[x]) st[++tp]=x;	//beginning node
			else add_edge(rt[x],j);
//			printf("%d(%d) %d\n",rt[x],x,j);
		}
		Ll=nwR+1;Rl=nd;
		while(ct) {rt[tt[ct]]=0;ct--;}
//		printf("[%d,%d]\n",Ll,Rl);
	}
}
ll dp[M];
void DP(int u) {
	dp[u]=1;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		DP(v);
		dp[u]=dp[u]*dp[v]%mod;
	}
	dp[u]++;
}
int main() {
//	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
	Build();
	ll ans=1;
	while(tp) {
		int i=st[tp--];
		DP(i);ans=ans*dp[i]%mod;
	}
	printf("%lld",ans);
	return 0;
}

T3.[USACO20JAN] Non-Decreasing Subsequences P

P6009

  • 题意:给每一个长为n的数列,每个数的上限是k,Q次问[li,ri]内的最长不降子序列的方案数

  • 思路:
    动态dp
    线段树维护z[x][y]表示上升子序列的权值为从xy的方案数。
    O(qlog2n k2)
    难写又慢,而且发现玄机(qn20倍)
    想要把询问q复杂度,转到n上。接下来用到:
    中轴分治自己乱创的,类似cdq
    即分治时每次处理跨过mid=(l+r)/2的区间
    然后这个区间的结果为mid左边的部分和右边的部分合并起来
    因此要预处理从mid往左的答案,从mid往右的答案
    如果处理和查询的每一步是O(1)的话,这个算法的复杂度是O(nlog2n+q)
    这样就能把q的复杂度转化到n上呢。
    合并显然需要:
    G[i][y]:(右半边)开始的值为y,前缀i的总方案数
    F[i][y]:(左半边)结束的值为y,后缀i的总方案数
    这里只说右半边(G)的做法,F同理
    方便G的转移,我们定义g[x][y]为上面的z[x][y]的意思
    然后每次新添加ai时:只有g[y][ai]  (1<=y<=ai)会受影响。
    要找前面的一个z<=ai
    g[y][ai]+=z=1aig[y][z]
    还要考虑ai独立为一个子序列
    g[ai][ai]++
    这样O(k2)处理了g
    G[i][y]=z=ykg[y][z] 知道开头y枚举结尾
    O(k2)而且这个跑的挺满的,实际需要O(k)就可以啦。
    G[i1][y]相比G[i][y]只有在y<=ai时,g[y][ai]的改变会影响G[i][y],此时加上g[y][ai]的变化量即可。
    总复杂度nlognk2+qk而且肯定是跑不满的。
    我翻了翻题解,题解搞了数据结构来优化到nklognlogk实际上因为数据结构卡死了上限还跑的比我慢很多。

  • code

戳我
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
const int M=N>>2;
const int K=21;
const int mod=1e9+7;
int a[N],b[N],q,n,k,rev[N],A[N],t1[N],t2[N];
//g[x][y]:(val)[x...y]; G[i][y]:(val)[y...],pre(pos) i
ll g[K][K],f[K][K],G[M][K],F[M][K],sG[M][K],ans[N];
struct query {int l,r;}Q[N];
void gt_gG(int l,int r) {
	g[a[l]][a[l]]=G[l][a[l]]=1;
	for(int i=l+1;i<=r;i++) {
		int ai=a[i];
		for(int y=1;y<=ai;y++) {
			G[i][y]=G[i-1][y]-g[y][ai];
			ll tmp=g[y][ai]+(y==ai);
			for(int z=y;z<=ai;z++) tmp+=g[y][z];
			g[y][ai]=(tmp%=mod);
			G[i][y]=(G[i][y]+tmp)%mod;
		}
		for(int y=ai+1;y<=k;y++) {G[i][y]=G[i-1][y];}
	}
}
void gt_fF(int l,int r) {
	f[b[l]][b[l]]=F[l][b[l]]=1;
	for(int i=l+1;i<=r;i++) {
		int bi=b[i];
		for(int y=bi;y<=k;y++) {
			F[i][y]=F[i-1][y]-f[bi][y];
			ll tmp=f[bi][y]+(y==bi);
			for(int z=bi;z<=y;z++) tmp+=f[z][y];
			f[bi][y]=(tmp%=mod);
			F[i][y]=(F[i][y]+tmp)%mod;
		}
		for(int y=1;y<bi;y++)F[i][y]=F[i-1][y];
	}
}
void Clear(int l,int r,int L,int R) {
	for(int i=1;i<=k;i++)for(int j=1;j<=k;j++)g[i][j]=f[i][j]=0;
	for(int i=l;i<=r;i++)for(int j=1;j<=k;j++)F[i][j]=0;
	for(int i=L;i<=R;i++)for(int j=1;j<=k;j++)sG[i][j]=G[i][j]=0;
}
ll SG(int i,int j) {
	if(sG[i][j])return sG[i][j];
	for(int y=1;y<=j;y++) sG[i][j]=(sG[i][j]+G[i][y])%mod;
	return sG[i][j];
}
void solve(int l,int r,int L,int R) {
	int mid=(l+r)>>1;
	gt_gG(mid+1,r);gt_fF(rev[mid],rev[l]);
	int c1=L,c2=R;
	for(int i=L;i<=R;i++) {
		int x=A[i];
		if(Q[x].l==Q[x].r) ans[x]=2;
		else if(Q[x].l>mid) t2[c2--]=x;
		else if(Q[x].r<=mid) t1[c1++]=x;
		else {
			int st=rev[Q[x].l],ed=Q[x].r;
			ans[x]=1+SG(ed,k);
			for(int yl=1;yl<=k;yl++) {
				ans[x]=(ans[x]+F[st][yl]*(SG(ed,k)-SG(ed,yl-1)+1))%mod;
			}
		}
	}
	Clear(rev[mid],rev[l],mid+1,r);
	if(c1!=L) {
		for(int i=L;i<c1;i++) A[i]=t1[i];
		solve(l,mid,L,c1-1);
	}
	if(c2!=R) {
		for(int i=R;i>c2;i--) A[i]=t2[i];
		solve(mid+1,r,c2+1,R);
	}
}

int main() {
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) {rev[i]=n-i+1;b[rev[i]]=a[i];}
//	for(int i=1;i<=n;i++)printf("%d ",b[i]);puts("");
	scanf("%d",&q);
	for(int i=1;i<=q;i++) {scanf("%d%d",&Q[i].l,&Q[i].r);A[i]=i;}
	solve(1,n,1,q);
	for(int i=1;i<=q;i++) printf("%lld\n",(ans[i]+mod)%mod);
	return 0;
}