Loading

CSP 后多校十五

A. 法阵

容易发现填放 \(1\) 的位置一定是两个类似三角形的地方.

问题可以巧妙的转化为放 \(1\) 的地方和放 \(0\) 的地方形成了不同的连通块,

且放 \(1\) 连通块的个数和放 \(0\) 连通块的个数都不超过 \(2\).

于是走格子计数就好.

A_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
	#define int long long 
	#define lf long double
	#define pb push_back
	#define kap make_pair
	#define lb lower_bound
	#define ub upper_bound
	#define Fill(x,y) memset(x,y,sizeof(x))
	#define Copy(x,y) memcpy(x,y,sizeof(y))
	#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
	auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
		while(!isdigit(ch)) cit|=(ch=='-'),ch=getchar();
		while(isdigit(ch)) w=(w<<3)+(w<<1)+(ch^48),ch=getchar();
		return cit?(-w):w;
	};
}using namespace BSS;

const int mod=998244353,N=4e3+21;

int m,n,ans,sum;
int C[N][N];
signed main(){
	File(magic);
	n=read(),m=read();
	for(int i=0;i<=4e3;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++){
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
		}
	}
	for(int i=1;i<n;i++){
		sum=0;
		for(int j=1;j<m;j++){
			sum=(sum+C[i-1+m-j][i-1]*C[i+j-1][j-1]%mod)%mod;
			ans=(ans+sum*C[n-i-1+j][j]%mod*C[n-i+m-j-1][m-j-1]%mod)%mod;
		}
	}
	int z=m; m=n,n=z;
	for(int i=1;i<n;i++){
		sum=0;
		for(int j=1;j<m;j++){
			ans=(ans+sum*C[n-i-1+j][j]%mod*C[n-i+m-j-1][m-j-1]%mod)%mod;
			sum=(sum+C[i-1+m-j][i-1]*C[i+j-1][j-1]%mod)%mod;
		}
	}
	printf("%lld\n",(ans<<1ll)%mod),exit(0);
}

B. 连通块

很自然的就觉得应该倒着做.

树上距离某个点最远的点一定是直径的端点之一.

所以并查集合并的时候维护一下直径端点就可以了.

B_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
	#define int long long 
	#define lf long double
	#define pb push_back
	#define kap make_pair
	#define lb lower_bound
	#define ub upper_bound
	#define Fill(x,y) memset(x,y,sizeof(x))
	#define Copy(x,y) memcpy(x,y,sizeof(y))
	#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
	auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
		while(!isdigit(ch)) cit|=(ch=='-'),ch=getchar();
		while(isdigit(ch)) w=(w<<3)+(w<<1)+(ch^48),ch=getchar();
		return cit?(-w):w;
	};
}using namespace BSS;

#define y1 asas
#define y2 sasa
const int N=2e5+21;

int m,n,ts,rt,lg2;
int head[N],fa[N],dep[N],vis[N],sep[N];
int d[N][2],st[N][25];
vector<int> ans,son[N];
struct I { int opt,x; } p[N];
struct II { int u,v,w,nxt; } e[N<<1];
int fond(int x){ return fa[x]==x ? x : fa[x]=fond(fa[x]); }
auto add=[](int u,int v)->void{
	e[++ts].u=u,e[ts].v=v,e[ts].nxt=head[u];
	head[u]=ts;
};
void dfs(int u,int dad){
	st[u][0]=dad,dep[u]=dep[dad]+1,fa[u]=u;
	for(int i=1;i<=lg2 and st[u][i-1];i++){
		st[u][i]=st[st[u][i-1]][i-1];
	}
	for(int i=head[u],v;i;i=e[i].nxt){
		if(v=e[i].v,v==dad) continue;
		dfs(v,u);
	}
}
auto lca=[](int x,int y)->int{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=lg2;~i;i--){
		if(dep[st[x][i]]>=dep[y]) x=st[x][i];
	}
	if(x==y) return x;
	for(int i=lg2;~i;i--){
		if(st[x][i]^st[y][i]) x=st[x][i],y=st[y][i];
	}
	return st[x][0];
};
auto getdis(int u,int v)->int{
	return dep[u]+dep[v]-dep[lca(u,v)]*2;
};
void sch(int u,int dad){
	son[rt].pb(u),fa[fond(u)]=rt;
	sep[u]=sep[dad]+1,vis[u]=1;
	for(int i=head[u],v;i;i=e[i].nxt){
		if(v=e[i].v,e[i].w or v==dad) continue;
		sch(v,u);
	}
}
auto solve=[](int x)->void{
	int u,v,w; rt=x,sch(x,0),u=0;
	for(auto i : son[x]) if(sep[i]>sep[u]) u=i;
	son[x].clear(),sch(u,0),v=0;
	for(auto i : son[x]) if(sep[i]>sep[v]) v=i;
	d[x][0]=u,d[x][1]=v;
};
signed main(){
	File(block);
	n=read(),m=read(),ts=1,lg2=log2(n)+1; 
	int u,v,w,x,y,z,x1,x2,y1,y2,w1,w2,w3,w4,w5,w6;
	for(int i=2;i<=n;i++){
		u=read(),v=read(),add(u,v),add(v,u);
	}
	for(int i=1;i<=m;i++){
		p[i].opt=read(),p[i].x=read();
		if(p[i].opt&1) e[p[i].x*2].w=1,e[p[i].x*2+1].w=1;
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	dfs(1,0);
	for(int i=1;i<=n;i++){
		if(!vis[i]) solve(i);
	}
	for(int i=m;i>=1;i--){
		x=p[i].x;
		if(p[i].opt&1){
			u=fond(e[x*2].u),v=fond(e[x*2].v),fa[u]=v;
			x1=d[u][0],y1=d[u][1],x2=d[v][0],y2=d[v][1];
			w1=getdis(x1,y1),w2=getdis(x1,x2),w3=getdis(x1,y2);
			w4=getdis(y1,x2),w5=getdis(y1,y2),w6=getdis(x2,y2);
			w=max(max(w1,w2),max(w3,w4)),w=max(w,max(w5,w6));	
			if(w==w1) d[v][0]=x1,d[v][1]=y1;
			else if(w==w2) d[v][0]=x1,d[v][1]=x2;
			else if(w==w3) d[v][0]=x1,d[v][1]=y2;
			else if(w==w4) d[v][0]=y1,d[v][1]=x2;
			else if(w==w5) d[v][0]=y1,d[v][1]=y2;
			else if(w==w6) d[v][0]=x2,d[v][1]=y2;
		}
		else{
			u=d[fond(x)][0],v=d[fond(x)][1];
			ans.pb(max(getdis(x,u),getdis(x,v)));
		}
	}
	for(int i=ans.size()-1;~i;i--) printf("%lld\n",ans[i]);
	exit(0);
}

C. 军队

不难想到要差分,于是线段树维护前 \(k\) 小即可.

C_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
	// #define int long long 
	#define lf long double
	#define pb push_back
	#define kap make_pair
	#define lb lower_bound
	#define ub upper_bound
	#define Fill(x,y) memset(x,y,sizeof(x))
	#define Copy(x,y) memcpy(x,y,sizeof(y))
	#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
	auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
		while(!isdigit(ch)) cit|=(ch=='-'),ch=getchar();
		while(isdigit(ch)) w=(w<<3)+(w<<1)+(ch^48),ch=getchar();
		return cit?(-w):w;
	};
}using namespace BSS;

#define LL long long 
#define ls (x<<1)
#define rs (x<<1|1)
#define y1 asas
#define y2 sasa
#define fi first
#define se second
const int N=3e5+21,inf=1e9;	

int m,n,s,t,ops;
LL pre[N][2];
pair<int,int> sx[N];
vector<pair<int,int> > inc[N],del[N];
struct I { int is,lzy,sum; pair<int,int> w[12]; } tr[N<<2];
auto ksm=[](int a,int b,int w=1)->int{
	for(;b;b>>=1,a=a*a) if(b&1) w=w*a;
	return w;
};
void build(int x,int l,int r){
	tr[x].is=1,tr[x].w[1].fi=0,tr[x].w[1].se=r-l+1,tr[x].sum=(r-l+1)*(t>0);
	if(l==r) return ; int mid=(l+r)>>1;
	build(ls,l,mid),build(rs,mid+1,r);
}
auto put=[](int x)->void{
	cout<<"x:"<<x<<' '<<tr[x].is<<' '<<tr[x].w[1].fi<<' '<<tr[x].w[1].se<<' '<<tr[x].w[2].fi<<" "<<tr[x].w[2].se<<endl;
};
auto getval=[](int x,int w)->void{
	tr[x].sum=0,tr[x].lzy+=w;
	for(int i=1,lmi=tr[x].is;i<=lmi;i++) tr[x].w[i].fi+=w;
	for(int i=1,lmi=tr[x].is;i<=lmi and tr[x].w[i].fi<t;i++) tr[x].sum+=tr[x].w[i].se;
	// cout<<"x and w:"<<x<<' '<<w<<" "<<tr[x].is<<' '<<tr[x].w[tr[x].is].fi<<endl;
};
auto spread=[](int x)->void{
	int &lzy=tr[x].lzy; if(!lzy) return ;
	getval(ls,lzy),getval(rs,lzy),lzy=0;
};
auto pushup=[](int x)->void{
	int i=1,j=1,k=0,lmi=tr[ls].is,lmj=tr[rs].is; 
	tr[x].sum=0,tr[x].w[0].fi=-1;
	while(k<t and (i<=lmi or j<=lmj)){
		while(k<t and i<=lmi and (tr[ls].w[i].fi<=tr[rs].w[j].fi or j>lmj)){
			if(tr[ls].w[i].fi==tr[x].w[k].fi) tr[x].w[k].se+=tr[ls].w[i++].se;
			else tr[x].w[++k]=tr[ls].w[i++];
		}
		if(k<t and j<=lmj){
			if(tr[rs].w[j].fi==tr[x].w[k].fi) tr[x].w[k].se+=tr[rs].w[j++].se;
			else tr[x].w[++k]=tr[rs].w[j++];
		}
	}
	tr[x].is=k;
	while(tr[ls].w[i].fi==tr[x].w[k].fi and i<=lmi) tr[x].w[k].se+=tr[ls].w[i++].se;
	while(tr[rs].w[j].fi==tr[x].w[k].fi and j<=lmj) tr[x].w[k].se+=tr[rs].w[j++].se;
	for(i=1;i<=k and tr[x].w[i].fi<t;i++) tr[x].sum+=tr[x].w[i].se;
	// put(x),put(ls),put(rs);
};
void upd(int x,int l,int r,int ql,int qr,int w){
	// cout<<x<<' '<<l<<' '<<r<<' '<<ql<<' '<<qr<<endl;
	if(l>=ql and r<=qr) return getval(x,w),void();
	int mid=(l+r)>>1; spread(x);
	if(ql<=mid) upd(ls,l,mid,ql,qr,w);
	if(qr>mid) upd(rs,mid+1,r,ql,qr,w);
	pushup(x); 
}
signed main(){
	File(army);
	n=read(),m=read(),s=read(),t=read(),ops=read(); 
	int w,x,y,z,x1,y1,x2,y2;
	for(int i=1;i<=s;i++){
		x1=read(),y1=read(),x2=read(),y2=read();
		inc[x1].pb(kap(y1,y2)),del[x2+1].pb(kap(y1,y2));
	}
	build(1,1,m);
	for(int i=1;i<=n;i++){
		for(auto j : inc[i]) upd(1,1,m,j.fi,j.se,1);
		for(auto j : del[i]) upd(1,1,m,j.fi,j.se,-1);
		sx[i].se=tr[1].sum,sx[i].fi=min(tr[1].sum,m-tr[1].sum);
		// cout<<"sum:"<<tr[1].sum<<endl;
	}
	sort(sx+1,sx+1+n,[](pair<int,int> i,pair<int,int> j){ return i.fi>j.fi; });
	for(int i=1;i<=n;i++) pre[i][0]=pre[i-1][0]+1ll*sx[i].fi,pre[i][1]=pre[i-1][1]+1ll*sx[i].fi*sx[i].fi;
	sort(sx+1,sx+1+n);
	while(ops--){
		x=read(),y=read(),z=lb(sx+1,sx+1+n,kap(y/2,0))-sx-1;
		int tmp=n-z; LL res=1ll*(y/2ll*(y-y/2ll))*min(x,tmp);
		if(x>tmp) res+=(pre[x][0]-pre[tmp][0])*y-(pre[x][1]-pre[tmp][1]);
		printf("%lld\n",res);
	}
	exit(0);	
}

D. 棋盘

暴力的做法就是 \(O(nq^2)\)\(dp\).

想办法少去枚举一些重复的信息.

观察数据范围,发现复杂度最多可以写 \(O(qn^2)\),或者带上个 \(log\).

于是不难想到每次都要利用之前的 \(dp\),但是每次删除和添加的操作又需要维护.

这个时候需要观察题目的性质,仔细思考可以知道这个马从 \((i,j)\) 点跳跃到 \((x,y)\) 点的过程是可逆的.

过程可逆,也就意味着方案数相同.

于是可以选择维护两个类似于对顶堆的东西.

\(dp_{i,j,x,y}\) 表示 从 \((x,y)\) 跳到 \((i,j)\) 的方案数.

考虑优化,发现 \(x\) 可以直接设为堆顶的位置 \(mid\),于是 \(dp_{i,j,y}\) 表示 从 \((mid,y)\) 跳到 \((i,j)\) 的方案数.

具体实现,针对向上还是向下,是否跨过 \(mid\) 进行讨论.

D_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS{
	#define int long long 
	#define lf long double
	#define pb push_back
	#define kap make_pair
	#define lb lower_bound
	#define ub upper_bound
	#define Fill(x,y) memset(x,y,sizeof(x))
	#define Copy(x,y) memcpy(x,y,sizeof(y))
	#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
	auto read=[](int w=0,bool cit=0,char ch=getchar())->int{
		while(!isdigit(ch)) cit|=(ch=='-'),ch=getchar();
		while(isdigit(ch)) w=(w<<3)+(w<<1)+(ch^48),ch=getchar();
		return cit?(-w):w;
	};
}using namespace BSS;

const int N=21,Q=1e5+21,mod=998244353;

char ch[N];

int m,n,l,r,mid,ops,ans;
int ma[Q][N];
int dp[2][Q][N][N];
auto inc=[](int &i,int j){ i+=j; i=i-(i>=mod)*mod; };
auto calc=[](int op,int x,int y,int z)->void{
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(!ma[x][i]) continue;
			if(i>2) inc(dp[op][x][i][j],dp[op][y][i-2][j]);
			if(i>1) inc(dp[op][x][i][j],dp[op][z][i-1][j]);
			if(i+2<=n) inc(dp[op][x][i][j],dp[op][y][i+2][j]);
			if(i+1<=n) inc(dp[op][x][i][j],dp[op][z][i+1][j]);
		}
	}
};
auto rebuild=[]()->void{
	mid=r;
	for(int i=l;i<=mid;i++) Fill(dp[0][i],0);
	for(int j=1;j<=n;j++) dp[0][mid][j][j]=ma[mid][j];
	for(int i=mid-1;i>=l;i--) calc(0,i,i+1,((i==mid-1) ? 0 : i+2));
	if(l==r) return ;
	for(int i=l;i<mid;i++) Fill(dp[1][i],0);
	for(int j=1;j<=n;j++) dp[1][mid-1][j][j]=ma[mid-1][j];
	for(int i=mid-2;i>=l;i--) calc(1,i,i+1,((i==mid-2) ? 0 : i+2));
};
auto Work=[]()->void{
	scanf("%s",ch+1);
	if(ch[1]=='A'){
		r++,scanf("%s",ch+1);
		for(int i=1;i<=n;i++) ma[r][i]=(ch[i]=='.');
		if(l==r) return rebuild(),void();
		if(r==mid+1){
			calc(0,r,r-1,0);
			for(int i=1;i<=n;i++) dp[1][r][i][i]=ma[r][i];
		}
		else{
			calc(0,r,r-1,r-2);
			calc(1,r,r-1,((r-2>mid) ? r-2 : 0));
		}
	}
	else if(ch[1]=='D'){
		l++; if(l>mid and mid<r) rebuild();
	}
	else if(ch[1]=='Q'){
		ans=0; int x=read(),y=read();
		if(!(ma[l][x] and ma[r][y])) return puts("0"),void();
		for(int i=1;i<=n;i++) inc(ans,dp[0][l][x][i]*dp[0][r][y][i]%mod);
		// cout<<"ans:"<<ans<<'\n';
		if(l<mid and r>mid){
			for(int i=1;i<=n;i++){
				if(i>1) inc(ans,dp[1][l][x][i]*dp[1][r][y][i-1]%mod);
				if(i+1<=n) inc(ans,dp[1][l][x][i]*dp[1][r][y][i+1]%mod);
			}
		}
		printf("%lld\n",ans);
	}
};
signed main(){
	File(chess);
	n=read(),l=1,r=0;
	for(int Ts=read();Ts;Ts--) Work();
	exit(0);
}

总结

这次考试又是沉迷于做一道题难以自拔,其实 \(T2\)\(T3\) 都比较简单.

另外能用数学知识解决的就不要老想着 \(dp\).

posted @ 2021-11-17 06:26  AaMuXiiiiii  阅读(64)  评论(0编辑  收藏  举报