CSP2019QwQ

咕咕咕~~~
话说这篇咕了好久到现在才开始写(暂时还是不会\(day1T3\),先欠着),后来到改才发现其实CSP2019不是很难?
先码题解

\(day1T1\)

傻逼题,不解释,确实对得起黄题的难度,这里思路是随便用个递归搞掉\(1A\)过算了
当然还可以用循环?,复杂度\(O(logk)\)?
代码:

#include<bits/stdc++.h>
using namespace std;

#define ll unsigned long long

inline void read(ll &x)
{
	x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}

ll fac[64];

inline void sol(ll n,ll k)
{
	if(n==1)
	{
		cout<<k?1:0;
		return;
	}
	if(k>=fac[n-1])
	{
		cout<<1;
		sol(n-1,fac[n-1]-k+fac[n-1]-1);
	}
	else
	{
		cout<<0;
		sol(n-1,k);
	}
}

int main()
{
	ll n,k;read(n);read(k);
	fac[0]=1;
	for(register int i=1;i<=63;++i) fac[i]=fac[i-1]<<1;
	sol(n,k);
	return 0;
}

\(day1T2\)

额,话说我考场上连这道绿题都没切掉
天理难容啊
果然还是太蒟蒻了
想出了正解思路,但是脑袋有点乱,不会码,于是从\(100pt\)变成了\(0pt\),难受
一句话题解:一点\(')'\)对答案的贡献是其对应的\('('\)的前一个符号的贡献\(+1\),开栈,回溯\(dfs\).
没了?~~没了 \(T\) _ \(T\) ……
代码\(O(n)\):

#include<bits/stdc++.h>
using namespace std;

#define ll long long

inline void read(int &x)
{
	x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}

const int xx=5e5+11;
int f[xx];
int head[xx],nxt[xx],to[xx],all=0;
ll k[xx],ctr[xx];
int sta[xx],top=0;
char ch[xx];

inline void dfs(int g)
{
	int tmp=0;
	if(ch[g]==')'&&top)
	{
		tmp=sta[top--];
		ctr[g]=ctr[f[tmp]]+1;
	}
	if(ch[g]=='(') sta[++top]=g;
	k[g]=k[f[g]]+ctr[g];
	for(register int v=head[g];v;v=nxt[v])
		dfs(to[v]);
	if(tmp) sta[++top]=tmp;
	if(ch[g]=='(') --top;
}

int main()
{
	int n;read(n);
	scanf("%s",ch+1);
	for(register int i=2;i<=n;++i)
	{
		read(f[i]);
		nxt[++all]=head[f[i]];
		to[all]=i;
		head[f[i]]=all;
	}
	dfs(1);
	ll ans=0;
	for(register int i=1;i<=n;++i)
		ans^=(i*k[i]);
	cout<<ans<<endl;
	return 0;
}

\(day1T3\)

咕咕咕~~~
妈呀,黑题,不码了
咕咕咕~~~
\(update~on~3rd~March~2020:\)
额,那啥,真香?
其实应该还是能比较容易地找到规律的吧

目标肯定是尽量把最小的数字给转移到最小的编号上,而且转移的方案(即转移路线)有且仅有一个。既然如此,就容易联想到贪心了:每次从最小的数字的数字出发找到能到达的最小节点,将其输出,再将路线记录下来

因为如果由于前面的路线使得当前路线不成立,那么当前路线一定不是最优路线,故贪心肯定是合法的

接下来考虑贪心的性质:
很容易可以想到一条路径应当划分为初始节点(初点),初点所连的在该路径上的边(初边),路径上的其它边(途边)与点(途点),以及末点与末边:

  1. 对于初边,它应当是初点的第一条被删的边,否则必定无法达成目标,所以任何一个点有且仅有一个初边
  2. 对于末边,它应当是末点的最后一条被删边,否则必定无法达成目标,同样任何一个点有且仅有一个末边
  3. 对于途边,如果有另一条同一路径上的途边与其连接在同一个途点上,那么对这个途点来说,其所连的所有边的删除顺序中,这两条途边必定是一先一后紧连的,并且一对途边一定只能转运一个数字

所以可以考虑用\(st[i](start)\)\(en[i](end)\)分别记录节点\(i\)的初边与末边,\(pre[i][j]\)\(nxt[i][j]\)(均为\(bool\)型)来记录点\(i\)的边\(j\)在点\(i\)连边的删边顺序中是否有连在一起的前驱与后继,以便我们对当前贪心方案的合法性进行判断

接下来看合法情况是怎样的:

假设现在我们遍历到了数字\(i\)的对应节点:
能将\(i\)作为初点的条件

  1. 目前没有初边或初边为当前遍历到的即将作为初边的边(预初边)
  2. 预初边在\(i\)的删除顺序中没有前驱
  3. 若预初边与\(i\)的末边已有直接的删除顺序连接,那么应当\(i\)的所有边的顺序都已确定(因为要将所有边删完)

能将\(i\)作为途点的条件

  1. 连接的两条途边分别在\(i\)无前驱与后继
  2. 两条途边没有直接的删除顺序连接
  3. 若靠前途边与\(i\)初边有直接删除顺序连接,靠后途边与\(i\)末边有直接删除顺序连接,那么应当除这两途边之外所有边的顺序都已确定(因为要将所有边删完)

能将\(i\)作为末点的条件

  1. 目前没有末边或末边为当前遍历到的即将作为末边的边(预末边)
  2. 预末边在\(i\)的删除顺序中没有后继
  3. 若预末边与\(i\)的初边已有直接的删除顺序连接,那么应当\(i\)的所有边的顺序都已确定(因为要将所有边删完)

可以发现,前驱与后继都只是需要查看是否有,故仅需对每个点的每条边开个\(bool\)型数组判断是否有。而每个点的初边与末边我们都是需要知道具体是哪个的,故要对每个点存下这个点的初边与末边编号

接下来就是一个小问题:是否有直接删除顺序相连。对此,容易想到使用并查集,当两条边确定了在某个点的严格先后顺序之后,将两条边在这个节点的并查集进行合并,同时可以使用并查集查看两条边是否在一个删边系统中

然后就是最后一个问题:如何确定当前节点删边顺序都已确定?

首先看删边顺序是怎么确定的:利用途边之间的连接得到一对途边在某一途点的严格先后顺序,然后一段段连接得到完全段。由此我们发现,当前节点的删边顺序是由一个个的严格删边顺序组合成的,而若一个点的删边顺序已完全确定,那么这个点对应得到的严格删边顺序数一定是该点度数减一。于是考虑记录每个点的度数,当确定一对严格删边顺序,度数减一

暴力枚举每个点与当前节点是否相通再取\(min\)的复杂度是\(O(n^3)\)的,我们当然不会这么傻,考虑每次以当前节点为根进行\(dfs\)遍历所有能到的点再取\(min\),复杂度降为\(O(n^2)\),而每次\(dfs\)时修改路径上每个点的父亲(\(f\))节点,这样在后面更新时会方便一点,直接从末点一个个往上蹦就行了

由于要开并查集,所以复杂度应当套个\(log\),但由于是对每个点的边开并查集,一条边最多开两次,用的次数也很有限,所以总的复杂度应该是\(O(Tn^2\alpha n)\)还是怀疑CCF老爷机跑不跑得过
代码:

#include<bits/stdc++.h>
using namespace std;

inline int read()
{
	int x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int xx=2e3+10;

struct node
{
	int fa[xx];
	bool pre[xx],nxt[xx];
	inline void init(int n)
	{
		for(register int i=1;i<=n;++i)
		{
			fa[i]=i;
			pre[i]=nxt[i]=0;
		}
	}
	inline int find(int i)
	{
		return i==fa[i]?i:fa[i]=find(fa[i]);
	}
	inline bool check(int i,int j)
	{
		return find(i)==find(j);
	}
	inline void merge(int i,int j)
	{
		int a=find(i),b=find(j);
		if(a!=b)
			fa[a]=b;
	}
}drop[xx];
int dot[xx],st[xx],en[xx],f[xx],f_l[xx],n;
int hd[xx],to[xx<<1],nt[xx<<1],du[xx],cnt;

inline void init()
{
	cnt=1;
	for(register int i=1;i<=n;++i)
		drop[i].init(n),f[i]=hd[i]=st[i]=en[i]=du[i]=0;
}

inline void add(int u,int v)
{
	nt[++cnt]=hd[u],to[hd[u]=cnt]=v;
	nt[++cnt]=hd[v],to[hd[v]=cnt]=u;
	++du[v],++du[u];
}

inline int dfs(int g,int jt)
{
	int res=xx+1;
	if(jt)
		if(!en[g]||en[g]==jt)
			if(!drop[g].nxt[jt])
				if(!(drop[g].check(jt,st[g])&&st[g]&&du[g]>1))
					res=g;
	for(register int i=hd[g],j;i;i=nt[i])
	{
		j=i>>1;
		if(j==jt)
			continue;
		if(!jt)
		{
			if(!st[g]||st[g]==j)
				if(!drop[g].pre[j])
					if(!(drop[g].check(j,en[g])&&en[g]&&du[g]>1))
						f[to[i]]=g,f_l[to[i]]=j,res=min(res,dfs(to[i],j));
		}
		else
		{
			if(jt!=en[g]&&j!=st[g]&&!drop[g].check(jt,j))
				if(!drop[g].nxt[jt]&&!drop[g].pre[j])
					if(!(du[g]>2&&st[g]&&en[g]&&drop[g].check(st[g],jt)&&drop[g].check(en[g],j)))
						f[to[i]]=g,f_l[to[i]]=j,res=min(res,dfs(to[i],j));
		}
	}
	return res;
}

inline void mate(int g)
{
	int jt;
	jt=en[g]=f_l[g];
	g=f[g];
	while(f[g])
	{
		drop[g].nxt[f_l[g]]=drop[g].pre[jt]=1;
		drop[g].merge(jt,f_l[g]);
		--du[g];
		jt=f_l[g];
		g=f[g];
	}
	st[g]=jt;
}

int main()
{
	int T=read();
	while(T--)
	{
		n=read();
                if(n==1)
                {
                    puts("1");
                    continue;
                }
		init();
		for(register int i=1;i<=n;++i)
			dot[i]=read();
		for(register int i=2,u,v;i<=n;++i)
			u=read(),v=read(),add(u,v);
		for(register int i=1,j;i<=n;++i)
		{
			f[dot[i]]=0;
			j=dfs(dot[i],0);
			mate(j);
			printf("%d ",j);
		}
		puts("");
	}
	return 0;
}

\(day2T1\)

话说硬是没看出来这是\(DP\),一直以为是数学,想到了容斥,结果没思路,想打份\(O(n2^m)\)的爆搜的,结果不知道哪里锅了,没调出来
自闭.jpg
于是看到\(DP\)就醍醐灌顶了
但想不到真的不能怪我啊,蒟蒻dp不好
容斥思路走一波,显然若有超过\(k/2\)的食材则仅有一种,于是总方案减不合法方案即为所求
\(s_i\)为第\(i\)行种类数总和,\(ex_{i,j}\)为第\(i\)行除\(a_{i,j}\)种类数总和,\(f_{i,j}\)为当前列(不合法列)\(DP\)到第\(i\)行,并且当前列的选择比其他列多\(j\)种的方案数(\(j\in [-n,n]\)),\(g_{i,j}\)为当前(整体)\(DP\)到第\(i\)行,并且已经选择了\(j\)种的方案数
易知:

\[f_{i,j}=f_{i-1,j}+f_{i-1,j-1}*a_{i,j}+f_{i-1,j+1}*ex_{i,j}~~~~~,~~~~~g_{i,j}=g_{i,j-1}*s_i \]

\[\text{若}all_f=\sum_{k=1}^{m}\sum_{j=1}^{n}f_{n,j}~~~,~~~\text{则}ans=\sum_{j=1}^{n}g_{n,j}-all_f \]

时间复杂度处理\(f_{i,j}\)\(O(mn^2)\)\(g_{i,j}\)\(O(n^2)\),合起来是\(O(mn^2)\),跑满大概\(2e7\)严重怀疑CCF老爷机跑不跑得动
上代码:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const ll mod=998244353;
const int xn=110,xm=2010;

inline void read(ll &x)
{
	x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}

ll a[xn][xm],s[xn],ex[xn][xm];
ll f[xn][xn<<1],g[xn][xn];
ll ans1=0,ans2=0,n,m;

inline void build_f()
{
	for(register int i=1;i<=m;++i)
	{
        memset(f,0,sizeof(f));
        f[0][n]=1;
		for(register int j=1;j<=n;++j)	
			for(register int k=n-j;k<=n+j;++k)
			{
				f[j][k]=f[j-1][k];
				(f[j][k]+=(f[j-1][k-1]*a[j][i]%mod))%=mod;
				(f[j][k]+=(f[j-1][k+1]*ex[j][i]%mod))%=mod;
			}
		for(register int k=n+1;k<=(n<<1);++k) (ans2+=f[n][k])%=mod;
	}
}
inline void build_g()
{
    g[0][0]=1;
	for(register int j=1;j<=n;++j)
		for(register int k=1;k<=j;++k)
		{
			g[j][k]=g[j-1][k];
			(g[j][k]+=(k>1?(g[j-1][k-1]*s[j]%mod):s[j]))%=mod;
		}
	for(register int k=1;k<=n;++k) (ans1+=g[n][k])%=mod;
}

int main()
{
	read(n);read(m);
	for(register int i=1;i<=n;++i)
	{
		s[i]=0;
		for(register int j=1;j<=m;++j)
		{
			read(a[i][j]);
			(s[i]+=a[i][j])%=mod;
		}
		for(register int j=1;j<=m;++j) ex[i][j]=((s[i]-a[i][j])%mod+mod)%mod;
	}
	build_f();
	build_g();
	ll ans=((ans1-ans2)%mod+mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

day2T2

day2T2好像也不是很难,就是切不掉而已
首先要证明一个大家考场上都应该想到了的显而易见的结论:当最后一段最小时肯定最优

\[\text{考虑用数学归纳法} \]

\[\text{先令}a+b=c~,~\text{其中}a<=b~,~\text{那么}a^2+b^2=(c-b)^2+b^2=2(b-\frac{c}{2})^2+\frac{c^2}{2} \]

\[\because a+b=c\land a<=b~,~\therefore b>=\frac{c}{2}~,~\therefore\text{当上式最小时}~,~b=b_{min}=\frac{c}{2} \]

同理可得整段亦如此,因为整段也是由一个个小段连成的
所以可以想到\(DP\),记录当前\(i\)的最优解对应的最后一段的和\(l_i\),同时需要前缀和\(s_i\)
那么遍历到\(i\)时,所求即为

\[\text{使得}l_k<=s_i-s_k\text{成立的最大的}k \]

移项可以发现条件变为

\[l_k+s_k<=s_i \]

由于\(l_k+s_k\)单增,所以可以用单调队列咕咕咕~ ~ ~
可是用了大整数(\(1e9\)压位)发现会\(T\),最多咕一个\(92pt\),貌似考后的某些关于\(ntt\)的想法还是没错的,如果没有\(luogu\) __ \(int128\)那这道题就萎了
(O2爷除外)
时间复杂度是非常友好的\(O(n)\)
代码(\(92pt\)\(1e9\)压位,把注释去掉可以用__\(int128\),或者可以就这样\(O2\)过)

#include<bits/stdc++.h>
using namespace std;

#define ll long long

inline ll read()
{
	ll x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
	return x;
}

const ll digit=1e9,mod=1ll<<30;
const int xx=4e7+11;

ll s[xx],que1[xx],ans[10],tmp[10],tmpp[10],hd=1,tl=0;
int a[xx],que2[xx];
int n,op;
ll x,y,z,b1,b2,m;
ll p,l,r,tmp_b;

inline void copy(ll num)
{
	tmp[0]=0;
	while(num)
	{
		tmp[++tmp[0]]=num%digit;
		num/=digit;
	}
	memset(tmpp,0,sizeof(tmpp));
	for(register int i=1;i<=tmp[0];++i)
		for(register int j=1;j<=tmp[0];++j)
		{
			tmpp[i+j-1]+=tmp[i]*tmp[j];
			if(tmpp[i+j-1]>=digit)
			{
				tmpp[i+j]+=tmpp[i+j-1]/digit;
				tmpp[i+j-1]%=digit;
			}
		}
	tmpp[0]=tmp[0]*2-1;
	for(register int i=1;i<=tmpp[0];++i)
		if(tmpp[i]>=digit)
		{
			tmpp[i+1]+=(tmpp[i]/digit);
			tmpp[i]%=digit;
		}
	while(!tmpp[tmpp[0]]) --tmpp[0];
}
inline void count()
{
	int k=max(ans[0],tmpp[0])+1;
	for(register int i=1;i<=k;++i)
	{
		ans[i]+=tmpp[i];
		if(ans[i]>=digit)
		{
			ans[i+1]+=(ans[i]/digit);
			ans[i]%=digit;
		}
	}
	while(!ans[k]) --k;
	ans[0]=k;
}

int main()
{
	n=read();op=read();
	if(!op)
		for(register int i=1;i<=n;++i)
			a[i]=read();
	else
	{
		x=read();y=read();z=read();
		b1=read();b2=read();m=read();
		for(register int i=1,j=1;i<=m;++i)
		{
			p=read(),l=read(),r=read();
			for(;j<=p;++j)
			{
				a[j]=b1%(r-l+1ll)+l;
				tmp_b=((x*b2%mod+y*b1%mod)%mod+z)%mod;
				b1=b2;b2=tmp_b;
			}
		}
	}
	for(register int i=1;i<=n;++i)
		s[i]=s[i-1]+a[i],a[i]=0;
	que2[++tl]=0;que1[tl]=0;
	que2[++tl]=1;que1[tl]=2ll*s[1];
	for(register ll i=2,j;i<=n;++i)
	{
		while(que1[hd+1]<=s[i]&&hd+1<=tl) ++hd;
		a[i]=que2[hd];
		j=2ll*s[i]-s[a[i]];
		while(j<=que1[tl]) --tl;
		que2[++tl]=i;que1[tl]=j;
	}
	for(register int i=n,j=a[n];i;i=a[i],j=a[j])
		copy(s[i]-s[j]),count();
//	__int128 res=0;
//	for(register int i=n,j=a[n];i;i=a[i],j=a[j])
//		res+=((__int128)(s[i]-s[j]))*(s[i]-s[j]);
//	while(res)
//		ans[++ans[0]]=res%10,res/=10;
	for(register int i=ans[0];i>=1;--i)
		printf("%d",ans[i]);
	puts("");
	return 0;
}

\(day2T3\)

考试的时候还是只会打暴力,蒟蒻~ ~ ~
关于正解,可以倍增
首先要明确一个性质,对于一棵树上的某个点,假设它是根,那么当它不是这棵树重心时,重心必在它重儿子所在的子树上
归纳一下,当它不是根时,那么重心一定在全树除该子树或者其重儿子子树上
通过比较一下\(sz[root]-sz[i]\)\(sz[son[i]]\)的大小就可以了
于是可以考虑用倍增跳这个路径,直到找到重心为止
考虑\(dfs\)枚举每条边要删的边,再从这条边的两头跳重心,由于可能要删的边截取了一个点的重儿子,所以我们的次重儿子也要存好,方便找到真正的路径,同时更新我们的倍增路径与子树大小,然后再慢慢跳
跳完之后注意处理与重心相连的几个点也有可能成为重心
两次\(dfs\),先预处理再\(solve\),简单易懂,时间复杂度\(O(nlogn)\)
代码:

#include<bits/stdc++.h>
using namespace std;

#define ll long long

inline int read()
{
	int x=0;char ch=getchar();
	for(;!isalnum(ch);ch=getchar());
	for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int xx=3e5;

int hd[xx],nxt[xx<<1],to[xx<<1],all;
int f[xx],son_1[xx],son_2[xx],sz[xx];
int d_f[xx],d_son[xx],d_sz[xx],way[xx][20];
ll ans=0;

inline ll test(int g,int num)
{
	ll res=(max(d_sz[d_son[g]],num-d_sz[g])<=num/2)?1:0;
	return res*g;
}
inline void count(int g)
{
	int w=g;
	for(register int i=19;~i;--i)
		if(d_sz[way[w][i]]>=ceil((double)d_sz[g]/2.0))
			w=way[w][i];
	ans+=(test(w,d_sz[g])+test(d_son[w],d_sz[g])+test(d_f[w],d_sz[g]));
}
inline void dfs_1(int g)
{
	sz[g]=1;
	for(register int u=hd[g],v;u;u=nxt[u])
	{
		v=to[u];
		if(v==f[g]) continue;
		f[v]=g;
		dfs_1(v);
		sz[g]+=sz[v];
		if(sz[v]>sz[son_1[g]]) son_2[g]=son_1[g],son_1[g]=v;
		else if(sz[v]>sz[son_2[g]]) son_2[g]=v;
	}
	way[g][0]=son_1[g];
	for(register int i=1;i<=19;++i)
		way[g][i]=way[way[g][i-1]][i-1];
}
inline void dfs_2(int g)
{
	for(register int u=hd[g],v;u;u=nxt[u])
	{
		v=to[u];
		if(v==f[g]) continue;
		d_f[g]=d_f[v]=0;
		d_sz[g]=sz[1]-sz[v];
		if(v==son_1[g]) d_son[g]=son_2[g];
		else d_son[g]=son_1[g];
		if(d_sz[d_son[g]]<d_sz[f[g]]) d_son[g]=f[g];
		way[g][0]=d_son[g];
		for(register int i=1;i<=19;++i)
			way[g][i]=way[way[g][i-1]][i-1];
		count(g),count(v);
		d_f[g]=v;
		dfs_2(v);
	}
	d_f[g]=f[g],d_sz[g]=sz[g],d_son[g]=way[g][0]=son_1[g];
	for(register int i=1;i<=19;++i)
		way[g][i]=way[way[g][i-1]][i-1];
}

int main()
{
	int T=read();
	while(T--)
	{
		memset(hd,0,sizeof(hd));
		memset(son_1,0,sizeof(son_1));
		memset(son_2,0,sizeof(son_2));
		memset(f,0,sizeof(f));
		int n=read();all=ans=0;
		for(register int i=2,u,v;i<=n;++i)
		{
			u=read(),v=read();
			nxt[++all]=hd[u],to[hd[u]=all]=v;
			nxt[++all]=hd[v],to[hd[v]=all]=u;
		}
		dfs_1(1);
		for(register int i=1;i<=n;++i)
			d_sz[i]=sz[i],d_son[i]=son_1[i],d_f[i]=f[i];
		dfs_2(1);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2020-03-21 10:03  wyd_forever  阅读(244)  评论(0)    收藏  举报