Loading

Noip模拟98 2021.11.14

T1 构造字符串

看出是图论,但是没有试着用冰茶姬,然后特判\(-1\)的还多了,就直接\(40pts\)

然后别人分数都很高就直接底垫垫了。。。。对,又一次底垫垫了

转化题意很简单,要求构造一串字典序最小的序列满足一些数相等,一些数不相等

那么直接分情况讨论,先把要求相等的数按照关系分成一堆联通块,然后每一条不相等的关系当做一条边将联通块连起来

不合法的判断只有一条,并没有我考场上想的那么麻烦,直接就如果联通块内有连边就是不合法

然后建完图之后就是变为了只有不相等的部分分(\(z_i=0\))的情况,那么直接贪心的按照编号从小往大扫,每次开个桶找个\(mex\)即可

考场上的\(SB\)做法就是因为没有使用冰茶姬合并相同的关系,而是把相同的和不同的放在了一起处理,这样会导致错误,\(Hack\)很好造,我很蠢。。。

说一下刚开始的时候想的假做法,没有保证字典序最小的时候可以对每一条关系整一个边权,不相等的关系边权为\(1\),相等的为\(0\),然后每个联通块跑一遍\(dfs\)就行了

str
#include<bits/stdc++.h>
#define int long long
using namespace std;FILE *wsn;
auto read=[](){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
};
const int NN=2005;
int n,m,ans[NN],bin[NN];
bool vis[NN];
struct node{
	int a,b,c;
	bool operator<(const node&x)const{
		return c>x.c;
	}
}op[NN];
vector<int>S[NN];
namespace DSU{
	int fa[NN];
	inline int getfa(int x){return fa[x]=((fa[x]==x)?x:getfa(fa[x]));}
	inline void merge(int x,int y){
		x=getfa(x); y=getfa(y);
		if(x==y) return;if(x>y) swap(x,y);
		fa[y]=x;
	}
}using namespace DSU;
namespace WSN{
	inline short main(){
		wsn=freopen("str.in","r",stdin);
		wsn=freopen("str.out","w",stdout);
		n=read(); m=read();
		for(int i=1;i<=n;i++)fa[i]=i;
		for(int i=1,a,b,c;i<=m;i++){
			a=read(),b=read(),c=read();
			if(a>b) swap(a,b);op[i]=node{a,b,c};
		}
		sort(op+1,op+m+1);
		for(int i=1;i<=m&&op[i].c!=0;i++){
			int a=op[i].a,b=op[i].b,c=op[i].c;
			for(int j=1,x,y;j<=c;j++)
				x=a+j-1,y=b+j-1,merge(x,y);
			if(b+c<=n) op[++m]=node{a+c,b+c,0};
		}
		for(int i=m;i>0&&op[i].c==0;i--){
			int a=op[i].a,b=op[i].b,c=op[i].c;
			if(getfa(a)==getfa(b))return puts("-1"),0;
			S[getfa(a)].push_back(getfa(b));
			S[getfa(b)].push_back(getfa(a));
		}
		for(int i=1;i<=n;i++)if(!vis[getfa(i)]){
			int mex=0,x=getfa(i); vis[x]=1;
			memset(bin,0,sizeof(bin));
			for(auto y:S[x])if(y<x) ++bin[ans[y]];
			while(bin[mex]) ++mex;
			ans[x]=mex;
		}
		for(int i=1;i<=n;i++)if(getfa(i)!=i)ans[i]=ans[getfa(i)];
		for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
		puts("");
		return 0;
	}
}
signed main(){return WSN::main();}

\(\color{white}{过于之菜了,已经连着好几天没有切题了,而且还都不是不会,就是打不对,很郁闷。。。。}\)

T2 寻宝

非常无脑的\(bfs+dfs\),切忌把带着数组的判断条件放在判断下标的前面,否则很可能\(RE\),比如

image

然后这种送分题都没能切,于是直接底垫垫。。。。。

然后我的打法比较难调,或者说我打的时候比较慌,出了很多zz错误,然后调了好久。。。后面的部分分没来及打,不打挂的话应该能拿\(50\)左右的样子。。。

剩下的思路就不说了,学过信队的,写过最短路的都可以轻松打过(除了我)

treasure
#include<set>
#include<map>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;FILE *wsn;
int mzs;
#define scanf mzs=scanf
auto read=[](){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
};
const int NN=50005;
int g[NN],n,m,k,q,in[NN],tot;
char ch[NN];
auto id=[](int x,int y){return (x-1)*m+y;};

struct door{int x1,y1,x2,y2;}d[105];

typedef pair<int,int> PII;
#define fi first
#define se second
#define mpr make_pair
queue<PII> Q;
map<int,PII>mp;
set<int> jump[NN];
bitset<NN>ee[NN];

int dx[5]={0,1,-1,0,0},
	dy[5]={0,0,0,1,-1};
bool vis[NN];
inline void walk(int x,int y){
	Q.push(mpr(x,y)); vis[id(x,y)]=1;
	in[id(x,y)]=++tot;
	while(!Q.empty()){
		int x=Q.front().fi,y=Q.front().se; Q.pop();
		for(int i=1;i<=4;i++){
			int xx=x+dx[i],yy=y+dy[i];
			if(xx>0&&yy>0&&xx<=n&&yy<=m&&!vis[id(xx,yy)]&&!g[id(xx,yy)]){
				Q.push(mpr(xx,yy));
				vis[id(xx,yy)]=1; in[id(xx,yy)]=tot;
			}
		}
	}
}
inline void dfs(int x){
	vis[x]=1;
	for(auto y:jump[x]){
		if(vis[y]) continue;
		dfs(y);
		ee[x]|=ee[y];
	}
}
namespace WSN{
	inline short main(){
		wsn=freopen("treasure.in","r",stdin);
		wsn=freopen("treasure.out","w",stdout);
		n=read(); m=read(); k=read(); q=read();
		for(int i=1;i<=n;i++){
			scanf("%s",ch+1);
			for(int j=1;j<=m;j++)
				g[id(i,j)]=(ch[j]=='#');
		}
		for(int i=1;i<=k;i++){
			int x1=read(),y1=read(),x2=read(),y2=read();
			d[i]=door{x1,y1,x2,y2};mp[id(x1,y1)]=mpr(x2,y2);
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(!vis[id(i,j)]&&!g[id(i,j)]){
					walk(i,j);
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(mp.find(id(i,j))!=mp.end()){
					PII now=mp[id(i,j)];
					jump[in[id(i,j)]].insert(in[id(now.fi,now.se)]);
					ee[in[id(i,j)]][in[id(now.fi,now.se)]]=1;
				}
			}
		}
		for(int i=1;i<=tot;i++){
			memset(vis,0,sizeof(vis));
			dfs(i);
		}
		for(int i=1;i<=q;i++){
			int x1=read(),y1=read(),x2=read(),y2=read();
			int i1=in[id(x1,y1)],i2=in[id(x2,y2)];
			if(i1==i2){puts("1");continue;}
			if(ee[i1][i2]){puts("1");}
			else puts("0");
		}
		return 0;
	}
}
signed main(){return WSN::main();}

T3 序列

李超线段树维护线段

考虑把过\(p\)区间分成右端点在\(p\)的一段最优区间加上左端点在\(p+1\)的一段最优区间

那么每次对答案的统计就变成了固定一个点,找另一个点,这样预计得到\(50\)

那么尝试化简式子

\(sa_i=\sum_{j=1}^i a_j\)\(sb_i=\sum_{j=1}^ib_j\)

最优即\(\max_{1\leq l\leq r}\{(sa_{r}-sa_{l-1})-k(sb_{r}-sb_{l-1})\}\)

\(sa_r-k\cdot sb_{r}-\min_{0\leq l<r}\{ksb_{l}-sa_l\}\),离线之后李超树维护直线即可

时间复杂度为\(O(n\log n)\),常数略大,空间复杂度为\(O(n)\)

seq
#include<bits/stdc++.h>
#define int long long
using namespace std; FILE *wsn;
auto read=[](){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
};
const int NN=2e6+5,inf=1e18;
const int lim=1e6;
int n,m,a[NN],b[NN],ans[NN],lst;
int sufa[NN],sufb[NN],prea[NN],preb[NN];
struct Query{
	int p,k,id;
	bool operator<(const Query&x)const{
		return p==x.p?k<x.k:p<x.p;
	}
}qu[NN];
namespace segment_tree{
	#define lid (id<<1)
	#define rid (id<<1|1)
	#define mid ((l+r)>>1)
	struct seg{
		int k,b;
		seg(){k=lim;b=inf;}
		seg(int k,int b):k(k),b(b){}
		int calc(int x){return k*x+b;}
	}tr[NN<<2];
	inline void update(seg x,int id=1,int l=-lim,int r=lim){
		if(tr[id].calc(mid)>x.calc(mid)) swap(tr[id],x);
		if(tr[id].calc(l)>x.calc(l))update(x,lid,l,mid);
		if(tr[id].calc(r)>x.calc(r))update(x,rid,mid+1,r);
	}
	inline int query(int pos,int id=1,int l=-lim,int r=lim){
		if(l>pos||r<pos) return inf;
		if(l==r) return tr[id].calc(pos);
		return min((mid<pos?query(pos,rid,mid+1,r):query(pos,lid,l,mid)),tr[id].calc(pos));
	}
}using namespace segment_tree;
namespace WSN{
	inline short main(){
		wsn=freopen("seq.in","r",stdin);
		wsn=freopen("seq.out","w",stdout);
		n=read(); m=read();
		for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
		for(int i=1;i<=n;i++)prea[i]=prea[i-1]+a[i],preb[i]=preb[i-1]+b[i];
		for(int i=n;i;i--)sufa[i]=sufa[i+1]+a[i],sufb[i]=sufb[i+1]+b[i];
		for(int o=1,p,k;o<=m;o++) p=read(),k=read(),qu[o]=Query{p,k,o};
		sort(qu+1,qu+m+1);
		lst=0;
		for(int i=1;i<=m;i++){
			Query x=qu[i];
			for(int j=lst;j<x.p;j++)
				update(seg{-preb[j],prea[j]});
			ans[x.id]=prea[x.p]-x.k*preb[x.p]-query(x.k);
			lst=x.p;
		}
		memset(tr,0,sizeof(tr));
		lst=n+1;
		for(int i=m;i;i--){
			Query x=qu[i];
			for(int j=lst;j>=x.p+1;j--)
				update(seg{-sufb[j],sufa[j]});
			ans[x.id]+=sufa[x.p+1]-x.k*sufb[x.p+1]-query(x.k);
			lst=x.p+1;
		}
		for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
		return 0;
	}
}
signed main(){return WSN::main();}

T4 构树

正在改。。。咕咕咕

改完了,非常难啊,不过还好题解写得好,很明白,于是改的就快一些,同时还有\(fengwu\)的大力帮助%%%

先粘题解,因为是\(markdown\)格式的嘿嘿嘿,因为写得好


(实际上应该是数树)

给了非多项式算法非常多分吧

Algorithm1:枚举

直接枚举所有的树,一共\(n^{n-2}\)种,根据不同枚举方法会带有不同的\(n^w\)

比如直接枚举prufer序列


Algorithm2:状压dp

假装我们的树是一颗以1为根的有根树,模拟展开dfs子树关系的过程

\(dp_{i,S,j}\)表示以\(i\)为根,子树里已经已经有\(S\)集合的节点,有\(j\)条边相同

然后为一个\(dp_{i,S,j}\)枚举一个儿子\(v\)以及其子树\(T\)进行合并,注意合并顺序防止重复

(比如可以保证\(S-\{i\}\)中最大编号的点<\(T\)中最大编号的点)

复杂度为\(O(3^n n^w)\)\(n\leq 15\)是给这个算法的


Algorithm3:Poly算法

要做多项式复杂度的算法,首先需要一点计数的辅助

Cayley公式:一个完全图有\(n^{n-2}\)棵无根生成树,经典问题prufer序列证明

扩展Cayley公式:被确定边分为大小为\(a_1,a_2,\cdots, a_m\)的连通块,则有\(n^{m-2}\prod {a_i}\)种生成树

证明就不再赘述了

那么我们可以通过 强制一条边出现或者是不出现 来统计边数

通过扩展Cayley公式统计强制的边能够得到多少树

比较Naive的想法,我们可以直接在dp中记录连通块大小、相同边数

每次断开一个连通块,乘上一个\(n\)的系数

强制相同的边,直接合并两个连通块即可

\(dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i+k,j+l+1}\)

强制不同的边,用不合并的-合并的即可得到

\(nk\cdot dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i,j+l}\)

\(-dp_{u,i,j}\cdot dp_{v,k,l}\rightarrow dp_{u,i+k,j+l}\)

预计分数:不知道


优化

WC数树

首先优化连通块大小,可以通过一个经典的转化:

\(size\)作为系数,转化为在这个连通块中选出一个关键点(很显然\(size\)多大,就有多少中选出一个关键点的方案)

这样这一位被压缩到\(0,1\),只需要记录是否在这个连通块中选择了关键点

这样复杂度等同于经典树形背包问题,但是空间略紧张


可以将第3维用拉格朗日插值换掉

设答案数列为\(a_i\),转化为多项式\(A(x)=\sum_{i=0} a_ix^i\)

带入\(x_0=0,1,\cdots ,n\),求得\(A(x_0)\)的数值,然后插值得到多项式

这样第三维就合并可以直接用\(x_0^k\)换掉


其实上我们只需要会那个扩展Cayley公式的部分就可以过掉这道题,再结合一下题解之外的二项式反演

按照如上的\(dp\)可以打出一个\(O(n^2)\)的树形\(dp\),空间复杂度\(O(n^2)\),数组开成\(dp[8001][2][8001]\),是优化的做法

但是不用拉格朗日插值,使用二项式反演

我们把\(dp\)关于相同边数的那一维变成至少有\(i\)条边,这样转移是不变的,可以感性理解一下,毕竟无法用数学方面的理解方式证明

那么我们做完\(dp\)之后算出至少有\(i\)条边和原树同构的方案数,即\(dp[1][1][i]\times \frac{1}{n}\),除以一个\(n\)是因为转移的时候累乘的那个\(n\)最后会多成一个

这样就做完了

tree
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace AE86{
	FILE *wsn;
	#define gc() p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
	char buf[1<<20],*p1=buf,*p2=buf;
	auto read=[](){
		int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f;
	};
	auto write=[](int x,char opt='\n'){
		char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
		do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
		for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
	};
}using namespace AE86;

const int NN=8005,mod=1e9+7;
int n,dp[NN][2][NN],g[NN],h[NN],v[NN],siz[NN];
struct SNOW{int to,next;};SNOW e[NN<<1]; int head[NN],rp;
auto add=[](int x,int y){e[++rp]=SNOW{y,head[x]};head[x]=rp;e[++rp]=SNOW{x,head[y]};head[y]=rp;};
auto qmo=[](int a,int b,int ans=1){for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;};
auto prework=[](){
	h[0]=h[1]=1;v[0]=v[1]=1;for(int i=2;i<NN;i++)h[i]=h[i-1]*i%mod;
	v[NN-1]=qmo(h[NN-1],mod-2);for(int i=NN-2;i>=2;i--)v[i]=v[i+1]*(i+1)%mod;
};auto C=[](int n,int m){return (n<m||n<0||m<0)?0:h[n]*v[n-m]%mod*v[m]%mod;};

int trans[NN][2][NN],ans[NN];
inline void dfs(int f,int x){
	dp[x][0][0]=dp[x][1][0]=1; siz[x]=1;
	for(int i=head[x],y=e[i].to;i;i=e[i].next,y=e[i].to)if(f!=y){dfs(x,y);
		for(int j=0;j<siz[x];j++)for(int k=0;k<siz[y];k++){
			trans[x][1][j+k+1]=(trans[x][1][j+k+1]+dp[x][1][j]*dp[y][0][k]%mod+dp[x][0][j]*dp[y][1][k]%mod)%mod;
			trans[x][0][j+k+1]=(trans[x][0][j+k+1]+dp[x][0][j]*dp[y][0][k]%mod)%mod;
			trans[x][0][j+k]=(trans[x][0][j+k]+dp[x][0][j]*dp[y][1][k]%mod*n%mod)%mod;
			trans[x][1][j+k]=(trans[x][1][j+k]+dp[x][1][j]*dp[y][1][k]%mod*n%mod)%mod;
		}
		siz[x]+=siz[y];
		for(int j=0;j<2;j++)for(int k=0;k<siz[x];k++)dp[x][j][k]=trans[x][j][k],trans[x][j][k]=0;
	}
}
namespace WSN{
	inline short main(){
		wsn=freopen("tree.in","r",stdin);wsn=freopen("tree.out","w",stdout);
		n=read(); for(int i=1,u,v;i<n;i++) u=read(), v=read(), add(u,v);
		prework();dfs(0,1);for(int i=0;i<n;i++)g[i]=dp[1][1][i]*qmo(n,mod-2)%mod;
		for(int i=0;i<n;i++)for(int j=i,bs=1;j<n;j++,bs*=-1)
			ans[i]=(ans[i]+bs*C(j,i)*g[j]%mod+mod)%mod;
		for(int i=0;i<n;i++)write(ans[i],' ');puts("");
		return 0;
	}
}
signed main(){return WSN::main();}
posted @ 2021-11-14 20:18  雪域亡魂  阅读(113)  评论(0编辑  收藏  举报