USACO20 JAN&FEB P

JAN 的 T3 too hard for me!

P6008 [USACO20JAN]Cave Paintings P

考虑水位不断上涨的过程即每次合并若干个列(点),注意到合并的答案等于 2 者的乘积,并查集维护一下即可。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
#define ID(x,y) (((x)-1)*m+(y))
using namespace std;
const int N=1005;
const int mod=(int)(1e9+7);
char e[N][N];
int n,m,tot,fa[N*N],f[N*N]; 

int fd(int x) {
	return x==fa[x]?x:fa[x]=fd(fa[x]);
}

void merge(int x,int y) {
	x=fd(x); y=fd(y);
	if(x!=y) fa[x]=y,f[y]=f[y]*f[x]%mod;
}

signed main() {
//	freopen("6.in","r",stdin);
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			cin>>e[i][j];
			if(e[i][j]=='.') f[ID(i,j)]=1;
			fa[ID(i,j)]=ID(i,j);
		}
	for(int i=n;i>=1;i--) {
		for(int j=1;j<=m;j++) {
			if(e[i][j]=='#') continue ;
			if(i+1<=n&&e[i+1][j]=='.') merge(ID(i+1,j),ID(i,j));
			if(j-1>=1&&e[i][j-1]=='.') merge(ID(i,j-1),ID(i,j));
			if(j+1<=m&&e[i][j+1]=='.') merge(ID(i,j+1),ID(i,j));
		}
		for(int j=1;j<=m;j++) {
			if(e[i][j]=='#') continue ;
			if(fd(ID(i,j))==ID(i,j)) ++f[ID(i,j)];
		}
	}
	int ans=1;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(e[i][j]=='#') continue ;
			if(fd(ID(i,j))==ID(i,j)) ans=ans*f[ID(i,j)]%mod;
		}
	}
	cout<<ans;
	return 0;
}

P6009 [USACO20JAN]Non-Decreasing Subsequences P

类猫树分治,但不需要记录每一层,考虑记起点是啥,上一个是啥,从中间向两边即可。

#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define ADD(x,y) ((x)+(y)>=mod?(x)+(y)-mod:(x)+(y))
using namespace std;
const int mod=(int)(1e9+7),N=(int)(5e4+5),M=(int)(2e5+5);
vector<pair<int,int> >vec[N];
int a[N],n,m,K,f[N][21][21],g[N][21][21],tmp[N],ans[M],val[N][22],val2[N][22];

void solve(int l,int r) {
	if(l==r) return ;
	int mid=(l+r)>>1;
	solve(l,mid); solve(mid+1,r);
	for(int i=l-1;i<=mid+1;i++) {
		memset(g[i],0,sizeof(g[i]));
		memset(val[i],0,sizeof(val[i]));
	}
	for(int i=mid;i>=l;i--) {
		for(int st=1;st<=K;st++) {
			for(int las=1;las<=K;las++) {
				g[i][st][las]=g[i+1][st][las];
			}
			if(st==a[i]) ++g[i][st][a[i]];
			for(int j=a[i];j<=K;j++) g[i][st][a[i]]=ADD(g[i][st][a[i]],g[i+1][st][j]);
			for(int j=1;j<=K;j++) val[i][st]=ADD(val[i][st],g[i][st][j]);
		}
	}
	for(int i=mid;i<=r+1;i++) {
		memset(f[i],0,sizeof(f[i]));
		memset(val2[i],0,sizeof(val2[i]));
	}
	for(int i=mid+1;i<=r;i++) {
		for(int st=1;st<=K;st++) {
			for(int las=1;las<=K;las++) {
				f[i][st][las]=f[i-1][st][las];
			}
			if(st==a[i]) ++f[i][st][a[i]];
			for(int j=1;j<=a[i];j++) f[i][st][a[i]]=ADD(f[i][st][a[i]],f[i-1][st][j]);
			for(int j=1;j<=K;j++) val2[i][st]=ADD(val2[i][st],f[i][st][j]);
		}
	}
	for(int i=mid+1;i<=r;i++) {
		while(!vec[i].empty()&&vec[i].back().first>=l) {
			int qwq=vec[i].back().first,id=vec[i].back().second;
//			cout<<l<<" "<<r<<" "<<i<<" "<<qwq<<'\n';
			ans[id]=1;
			for(int j=1;j<=K;j++)
				for(int k=j;k<=K;k++)
					ans[id]=ADD(ans[id],1ll*val[qwq][j]*val2[i][k]%mod);
			for(int j=1;j<=K;j++) ans[id]=ADD(ans[id],ADD(val[qwq][j],val2[i][j]));
			vec[i].pop_back();
		}
	}
}

signed main() {
//	freopen("22.in","r",stdin);
//	freopen("xgf.out","w",stdout);
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>K;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m;
	for(int i=1;i<=m;i++) {
		int l,r; cin>>l>>r;
		if(l==r) {
			ans[i]=2; continue ;
		}
		vec[r].pb(make_pair(l,i));
	}
	for(int i=1;i<=n;i++) sort(vec[i].begin(),vec[i].end());
	solve(1,n);
	for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
	return 0;
}

P6142 [USACO20FEB]Delegation P

考虑类似赛道修建,二分,变为判定性问题。不难发现一个点的子树内至多只能剩下一条链,这条链再去与祖先链配对。于是考虑从小到大尝试两两配对(小的能配成功的链比较少,贪心 trick),若非根但当前有奇数条子树链,考虑剩下的一条即可。若有偶数条,其实也有可能一条链单独满了,变为奇数条的情况,因此你加一个 \(0\) 链即可。

注意,根一定要配完!所以倘若是奇数条链的话,必须存在一条自己能满足条件的。

#include <bits/stdc++.h>
//#define int long long
#define pb push_back
using namespace std;
const int N=(int)(1e5+5);
vector<int>g[N];
int n,f[N],Lim;
bool FL;

void dfs(int x,int ff) {
	for(int y:g[x]) {
		if(y==ff) continue ;
		dfs(y,x);
		if(!FL) return ;
	}
	if(!FL) return ;
	multiset<int>s; s.clear();
	for(int y:g[x]) {
		if(y==ff) continue ;
		s.insert(f[y]+1);
	}
	if(s.empty()) return ;
	if(x!=1&&((s.size())%2==0)) {
		s.insert(0);
	}
	if(x==1&&(s.size())&1) s.insert(0);
	bool ok=1;
	while(!s.empty()) {
		int qwq=(*s.begin()); s.erase(s.begin());
		auto p=s.lower_bound(Lim-qwq);
//		cout<<qwq<<" "<<Lim<<'\n';
		if(p==s.end()) {
			if(ok) {
				ok=0; f[x]=qwq; continue ;
			} else {
				FL=0; break ;
			}
		} else {
			s.erase(p);
		}
	}
	if(x==1&&!s.empty()) {
		FL=0; return ;
	}
}

bool chk(int x) {
	Lim=x; FL=1;
	dfs(1,0);
	return FL;
}

signed main() {
//	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<n;i++) {
		int x,y; cin>>x>>y;
		g[x].pb(y); g[y].pb(x);
	}
	int l=1,r=n,res=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(chk(mid)) res=mid,l=mid+1;
		else r=mid-1;
	}
	cout<<res;
	return 0;
}

P6143 [USACO20FEB]Equilateral Triangles P

很厉害的题!

我一头栽进去转完切比雪夫距离后三角形的形状了。

image

考虑 A,B 所连直线 \(k\) 要么 \(1\) 要么 \(-1\),这是显然的。

你考虑转切比雪夫后画正方形,然后你会发现一定存在 2 个点所连边与 \(x\) 轴或者与 \(y\) 轴平行,然后你旋转 \(\dfrac{\pi}{4}\) 回去显然。

然后就是一个斜向前缀和了,注意下算 "2+1",即这 4 个端点选 2 个组合上一个非端点的,再算 "3" 的。

#include <bits/stdc++.h>
#define pb push_back
#define ll long long
#define int ll
using namespace std;
int a[605][605];
int n;
ll ans,sum1[605][605],sum2[1205][1205],tmp[1205][1205];

void sol1(int x,int y,int l) {
	int xx=x-l,yy=y-l;
	if(xx<1||yy<1) return ;
	if(!a[xx][y]||!a[x][yy]) return ;
//	for(int i=1;i<l;i++) {
//		int nwy=y+i,nwx=l-i+x;
//		if(nwx<1||nwy<1||nwx>n||nwy>n) continue ;
//		if(a[nwx][nwy]) ++ans;
//	}
	// [l-1+x,1+x] [y+1,y+l-1]
//	int nwx=x+1,nwy=y+l-1;
//	while(nwx<=x+l-1&&nwy>0) {
//		ans+=a[nwx][nwy];
//		++nwx; --nwy;
//	}
	if(1<l) ans+=sum2[x+l-1][y+1]-sum2[x][y+l];
}

void sol2(int x,int y,int l) {
	int xx=x-l,yy=y+l;
	if(xx<1||yy>n) return ;
	if(!a[xx][y]||!a[x][yy]) return ;
	if(1<=l-1) {
		int dwx=l-(l-1)+x-1,dwy=y-(l-1)-1;
		while(dwx<0||dwy<0) ++dwx,++dwy;
		if(dwx<0||dwy<0) {
			if(dwx<0&&dwy<0) {
				int qwq=max(-dwx,-dwy);
				dwx+=qwq; dwy+=qwq;
			} else if(dwx<0) {
				dwy+=-dwx; dwx=0;
			} else if(dwy<0) {
				dwx+=-dwy; dwy=0;
			}
		}
		ans+=sum1[l-1+x][y-1]-sum1[dwx][dwy];
	}
}

void sol3(int x,int y,int l) {
	int xx=x+l,yy=y+l;
	if(xx>n||yy>n) return ;
	if(!a[xx][y]||!a[x][yy]) return ;
//	for(int i=1;i<l;i++) {
//		int nwy=y-i,nwx=x-l+i;
//		if(nwx<1||nwy<1||nwx>n||nwy>n) continue ;
//		if(a[nwx][nwy]) ++ans;
//	}
	// x[x-l+1,x-1] y[y-l+1,y-1]
	int nwx=x-l+1,nwy=y-1;
	while(nwx<=x-1) {
		if(nwx>=0&&nwy>=0) ans+=a[nwx][nwy];
		++nwx; --nwy;
	}
//	if(1<l) {
//		cout<<x-1+n<<" "<<y-l+1+n<<" "<<x-l+n<<" "<<y+n<<'\n';
//		ans+=sum2[x-1+2*n][y-l+1+2*n]-sum2[x-l+2*n][y+2*n];
//	}
}

void sol4(int x,int y,int l) {
	int xx=x+l,yy=y-l;
	if(xx>n||yy<1) return ;
	if(!a[xx][y]||!a[x][yy]) return ;
	if(1<=l-1) {
		int dwx=x-l,dwy=y;
		while(dwx<0||dwy<0) ++dwx,++dwy;
		if(dwx<0||dwy<0) {
			if(dwx<0&&dwy<0) {
				int qwq=max(-dwx,-dwy);
				dwx+=qwq; dwy+=qwq;
			} else if(dwx<0) {
				dwy+=-dwx; dwx=0;
			} else if(dwy<0) {
				dwx+=-dwy; dwy=0;
			}
		}
		ans+=sum1[x-1][y+l-1]-sum1[dwx][dwy];
	}
}

void sol(int x,int y,int xx,int yy,int xxx,int yyy) {
	if(x<1||y<1||xx<1||yy<1||x>n||y>n||xx>n||yy>n||xxx<1||yyy<1||xxx>n||yyy>n) return ;
	ans+=((a[x][y]&a[xx][yy])&(a[xxx][yyy]));
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			char ch; cin>>ch;
			if(ch=='*') a[i][j]=1;
		}
	}
	for(int i=1;i<=2*n;i++) {
		for(int j=1;j<=2*n;j++) {
			sum1[i][j]=sum1[i-1][j-1]+a[i][j];
		}
		for(int j=2*n;j>=1;j--) {
			sum2[i][j]=sum2[i-1][j+1]+a[i][j];
		}
	}
//	for(int i=1;i<=2*n;i++)
//		for(int j=1;j<=2*n;j++)
//			sum2[i+2*n][j+2*n]=tmp[i][j];
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
//			if(!a[i][j]) continue ;
			for(int k=1;k<=n;k++) {
				sol1(i,j,k);
				sol2(i,j,k);
				sol3(i,j,k);
				sol4(i,j,k);
				int x=i,y=j;
				sol(x-k,y,x,y+k,x+k,y);
				sol(x,y+k,x+k,y,x,y-k);
				sol(x+k,y,x,y-k,x-k,y);
				sol(x,y-k,x-k,y,x,y+k);
			}
		}
	}
	cout<<ans;
	return 0;
}

P6144 [USACO20FEB]Help Yourself P

学到了,你要让线段以某种顺序加入计算联通线段的段的个数,你可以按 \(l\) 排序,这样子每次加入一定不会合并之前分离的线段段的。

image

考虑一下为啥直接做二项式定理是对的。

你考虑一个状态的 dp 值可能是 \(\sum_{i\in S} l_i^k\) 的形式的,然后你加 \(1\) 了,那么变为 \(\sum_{i\in S} (l_i+1)^k\),注意一下二项式定理,即 \(\sum_{i\in S} \sum _j \binom{k}{j} l_i^j\),然后你提出去,\(\sum_j \binom{k}{j}\sum_{i\in S}l_i^j\),记 \(f(S,i)\)\(\sum_{x\in S}l_x^i\),则前者变为 \(\sum_j\binom{k}{j}f(S,j)\),所以我们直接对答案做一次二项式定理是对的。综上,你只需要维护这个多项式 \([0,k]\) 次的答案即可。

#include <bits/stdc++.h>
#define pb push_back
#define int long long
#define ADD(x,y) ((x)+(y)>=mod?(x)+(y)-mod:(x)+(y))
using namespace std;
const int mod=(int)(1e9+7);
const int N=(int)(2e5+5);
struct Li {
	int l,r;
}p[N];
int n,K,jie[20],djie[20];

struct node {
	int a[12];
	node() {
		memset(a,0,sizeof(a));
	}
	void clr() {
		memset(a,0,sizeof(a));
	}
}f[N],g[N];

int C(int n,int m) {
	if(n<0||m<0||m>n) return 0;
	return jie[n]*djie[m]%mod*djie[n-m]%mod;
}

int fpow(int x,int y) {
	int res=1; x%=mod;
	while(y) {
		if(y&1) res=res*x%mod;
		y>>=1; x=x*x%mod;
	} return res;
}

node add(const node &x,const node &y) {
	node res;
	for(int i=0;i<=K;i++) res.a[i]=ADD(x.a[i],y.a[i]);
	return res;
}

node add1(const node &x) {
	node res;
	for(int i=0;i<=K;i++) {
		for(int j=0;j<=i;j++) {
			res.a[i]=ADD(res.a[i],x.a[j]*C(i,j)%mod);
		}
	}
	return res;
}

node mul(const node &x,int pp) {
	node res; int qwq=fpow(2,pp);
	for(int i=0;i<=K;i++) res.a[i]=x.a[i]*qwq%mod;
	return res;
}

bool cmp(const Li &x,const Li &y) {
	return x.l==y.l?x.r<y.r:x.l<y.l; //按 l 是因避免加入后使前面的联通段合并起来 
}
#define ls ((cur)<<1)
#define rs ((ls)|1)
node sum[N<<3];
int tag[N<<3];
void push_up(int cur) {
	sum[cur]=add(sum[ls],sum[rs]);
}
void build(int cur,int l,int r) {
	if(l==r) {
		sum[cur]=f[l]; return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid); build(rs,mid+1,r);
	push_up(cur);
}
void push_down(int cur) {
	if(!tag[cur]) return ;
	tag[ls]+=tag[cur]; tag[rs]+=tag[cur];
	sum[ls]=mul(sum[ls],tag[cur]);
	sum[rs]=mul(sum[rs],tag[cur]);
	tag[cur]=0;
}
void upt1(int cur,int l,int r,int p,const node &ad) {
	if(l==r) {
		sum[cur]=add(sum[cur],ad); return ;
	}
	push_down(cur); 
	int mid=(l+r)>>1;
	if(p<=mid) upt1(ls,l,mid,p,ad);
	else upt1(rs,mid+1,r,p,ad);
	push_up(cur);
}
void upt2(int cur,int l,int r,int cl,int cr) {
	if(cl<=l&&r<=cr) {
		++tag[cur]; sum[cur]=mul(sum[cur],1); return ;
	}
	push_down(cur);
	int mid=(l+r)>>1;
	if(cl<=mid) upt2(ls,l,mid,cl,cr);
	if(cr>mid) upt2(rs,mid+1,r,cl,cr);
	push_up(cur);
}
node qry(int cur,int l,int r,int cl,int cr) {
	if(cl<=l&&r<=cr) return sum[cur];
	push_down(cur);
	int mid=(l+r)>>1;
	if(cr<=mid) return qry(ls,l,mid,cl,cr);
	else if(cl>mid) return qry(rs,mid+1,r,cl,cr);
	else return add(qry(ls,l,mid,cl,cr),qry(rs,mid+1,r,cl,cr));
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>K; jie[0]=djie[0]=1;
	for(int i=1;i<20;i++) jie[i]=jie[i-1]*i%mod,djie[i]=fpow(jie[i],mod-2);
	for(int i=1;i<=n;i++) cin>>p[i].l>>p[i].r;
	sort(p+1,p+1+n,cmp);
	f[0].a[0]=1; build(1,0,2*n);
	for(int i=1;i<=n;i++) {
		int l=p[i].l,r=p[i].r;
		node qwq;
		if(l<=r-1) qwq=qry(1,0,2*n,l,r-1),upt1(1,0,2*n,r,qwq);
		if(r+1<=2*n) upt2(1,0,2*n,r+1,2*n);
		qwq=qry(1,0,2*n,0,l-1);
		qwq=add1(qwq);
		upt1(1,0,2*n,r,qwq);
//		for(int j=0;j<=2*n;j++) g[j]=f[j],f[j].clr();
//		for(int j=l;j<=r;j++) f[r]=add(f[r],g[j]);
//		node tmp;
//		for(int j=l;j<r;j++) f[j]=g[j];
//		for(int j=r+1;j<=2*n;j++) f[j]=mul2(g[j]);
//		for(int i=0;i<l;i++) tmp=add(tmp,g[i]),f[i]=g[i];
//		tmp=add1(tmp); f[r]=add(f[r],tmp);
	}
	int ans=0;
	for(int i=1;i<=2*n;i++) {
		node qwq=qry(1,0,2*n,i,i);
		ans=ADD(ans,qwq.a[K]);
	}
	cout<<ans;
	return 0;
}
posted @ 2022-10-14 16:33  FxorG  阅读(19)  评论(0编辑  收藏  举报