CSP-S 2019題解

退役老狗來做CSP題了。作爲一套選拔試題,感覺涉及的知識點不是太全面。但如果單就題目來看,還是值得一做的。

格雷碼

從高位往低位做,每次通過kk的當前位來得到這一位的答案。同時如果這一位的答案是11,則要把從前往後數變爲從後往前數。
時間複雜度O(n)O(n).

#include<bits/stdc++.h>

typedef unsigned long long ull;

int n;
ull k,bin[105];

int main()
{
	scanf("%d%llu",&n,&k);
	bin[0]=1;
	for (int i=1;i<n;i++) bin[i]=bin[i-1]*2;
	for (int i=n-1;i>=0;i--)
	{
		putchar((k&bin[i])?'1':'0');
		if (k&bin[i]) k-=bin[i],k=bin[i]-1-k;
	}
	return 0;
}

括號樹

先求出以每個節點作爲結尾有多少個合法的序列,然後再求前綴和。設當前節點上的字符是右括號,記錄以每個節點爲結尾的最長合法序列位置,找到從當前節點的父節點開始第一個無法被匹配的左括號來與當前節點匹配,則當前點的答案就是對應左括號節點的父節點答案+1+1.
時間複雜度O(n)O(n).

#include<bits/stdc++.h>
#define pb push_back

typedef long long LL;

const int N=500005;

int n,fa[N],nx[N];
LL f[N],sum[N];
std::vector<int> e[N];
char str[N];

void dfs(int x)
{
	if (str[x]==')')
	{
		int y=nx[fa[x]];
		if (y) f[x]=f[fa[y]]+1,nx[x]=nx[fa[y]];
	}
	else nx[x]=x;
	sum[x]=f[x]+sum[fa[x]];
	for (auto to:e[x]) dfs(to);
}

int main()
{
	scanf("%d",&n);
	scanf("%s",str+1);
	for (int i=2;i<=n;i++)
	{
		scanf("%d",&fa[i]);
		e[fa[i]].pb(i);
	}
	dfs(1);
	LL ans=0;
	for (int i=1;i<=n;i++) ans^=(LL)sum[i]*i;
	printf("%lld\n",ans);
	return 0;
}

樹上的數

對於一個點xx,只考慮xx的出邊,可以發現不同的刪邊順序必然對應着不同的結果,並且不同的點之間是可以分開考慮的。
很容易想到貪心,從小到大枚舉節點上的數字,再找到這個數字能到達的最小的節點編號,並把該編號作爲答案。問題在於如何判斷一個數字能到達哪些節點。
假設把數字iixx移到yyxxyy路徑上的點爲t1,,tst_1,\cdots,t_s,其中t1=x,ts=yt_1=x,t_s=y,則產生的限制爲
(1)(t1,t2)(t_1,t_2)這條邊一定是t1t_1的所有出邊中最先被刪掉的;
(2)(ts1,ts)(t_{s-1},t_s)這條邊一定是tst_s的所有出邊中最後被刪掉的;
(3)只考慮tit_i的出邊,(ti,ti+1)(t_i,t_{i+1})必然是在刪掉(ti1,ti)(t_{i-1},t_i)之後緊接着被刪掉。
不難驗證若加上所有限制後每個點均存在合法的刪邊序列,則這樣的操作是可行的。
換句話說,如果把一個節點的每條出邊都看作一個點,每個限制看成一條有向邊,則最終必然會形成若干條鏈。那麼只需要記錄每個點的入度,出度以及所在鏈的信息,就可以判斷加入一個限制後是否仍然合法。我的實現方法是並查集。
回到貪心算法,枚舉數字後,從該數字的初始位置出發dfs,找到所有能去到的點並求出最小值,再把限制全部加上即可。
時間複雜度O(n2)O(n^2).

#include<bits/stdc++.h>
#define pb push_back

const int N=2005;

int n,pos[N],f[N*N],ans[N],mn,top,stack[N],num[N],sz[N*N];
bool vis[N],in[N*N],out[N*N],tag[N*N],tag2[N*N];
std::vector<int> e[N];

int get(int x,int y)
{
	return (x-1)*n+y;
}

int find(int x)
{
	return f[x]==x?x:f[x]=find(f[x]);
}

void dfs1(int x,int fa)
{
	int u=find(get(x,fa));
	if (!vis[x]&&(!tag[u]||sz[u]==e[x].size())) mn=std::min(mn,x);
	for (auto to:e[x])
	{
		if (to==fa) continue;
		int v=find(get(x,to));
		if (tag[u]&&tag2[v]&&sz[u]+sz[v]<e[x].size()) continue;
		if (!in[get(x,to)]&&!out[get(to,x)]&&u!=v) dfs1(to,x);
	}
}

bool dfs2(int x,int fa,int tar)
{
	if (x==tar) {stack[++top]=x;return 1;}
	for (auto to:e[x])
		if (to!=fa&&dfs2(to,x,tar)) {stack[++top]=x;return 1;}
	return 0;
}

void clear()
{
	for (int i=1;i<=n;i++) e[i].clear(),vis[i]=0;
}

int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) scanf("%d",&pos[i]),num[pos[i]]=i;
		for (int i=1;i<n;i++)
		{
			int x,y;scanf("%d%d",&x,&y);
			e[x].pb(y);e[y].pb(x);
		}
		for (int i=1,p;i<=n;i++)
			for (auto to:e[i])
				p=get(i,to),f[p]=p,sz[p]=1,in[p]=out[p]=tag[p]=tag2[p]=0;
		for (int i=1;i<=n;i++)
		{
			int x=pos[i];mn=n+1;
			for (auto to:e[x])
			{
				int u=get(x,to),v=get(to,x);
				if (!in[u]&&!out[v]&&(!tag2[find(u)]||e[x].size()==sz[find(u)])) dfs1(to,x);
			}
			ans[i]=mn;
			vis[mn]=1;
			top=0;
			dfs2(x,0,mn);
			for (int j=2;j<top;j++)
			{
				int u=get(stack[j],stack[j-1]),v=get(stack[j],stack[j+1]);
				out[v]=in[u]=1;
				u=find(u);v=find(v);f[u]=v;tag[v]|=tag[u];tag2[v]|=tag2[u];sz[v]+=sz[u];
			}
			tag[find(get(stack[top],stack[top-1]))]=1;
			out[get(stack[1],stack[2])]=tag2[find(get(stack[1],stack[2]))]=1;
		}
		for (int i=1;i<=n;i++) printf("%d ",ans[i]);
		puts("");
		clear();
	}
	return 0;
}

Emiya 家今天的飯

考慮對食材的限制進行容斥,用所有方案減去恰有一種食材的使用數量超過一半的方案。枚舉這樣的食材xx,設fi,jf_{i,j}表示前ii中烹飪方法中,使用xx的菜數量減去不使用xx的菜數量爲jj的方案,轉移就是個簡單的揹包。
時間複雜度O(n2m)O(n^2m).

#include<bits/stdc++.h>

typedef long long LL;

const int N=105;
const int MOD=998244353;

int n,m,a[N][N*20],s[N],f[N][N*2];

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			scanf("%d",&a[i][j]),(s[i]+=a[i][j])%=MOD;
	int ans=1;
	for (int i=1;i<=n;i++) ans=(LL)ans*(s[i]+1)%MOD;
	ans--;
	for (int i=1;i<=m;i++)
	{
		f[0][n]=1;
		for (int j=1;j<=n;j++)
			for (int k=-j;k<=j;k++)
			{
				f[j][k+n]=f[j-1][k+n];
				(f[j][k+n]+=(LL)f[j-1][k-1+n]*a[j][i]%MOD)%=MOD;
				(f[j][k+n]+=(LL)f[j-1][k+1+n]*(s[j]-a[j][i])%MOD)%=MOD;
			}
		for (int j=1;j<=n;j++) (ans-=f[n][j+n])%=MOD;
	}
	printf("%d\n",(ans+MOD)%MOD);
	return 0;
}

劃分

大膽猜一波結論,我們肯定是讓最後那一段的值越小越好。設pip_i表示以ii爲結尾倒數第二段的結束位置,對於每一個的iipip_i顯然等於所有滿足sisjsjspjs_i-s_j\ge s_j-s_{p_j}中最大的jj。用一個單調隊列求出所有的pIp_I,然後順着pip_i往回跳並統計答案就好。
時間複雜度O(n)O(n).

#include<bits/stdc++.h>

typedef long long LL;

const int N=40000005;

int n,ty,b[N],f[N],que[N],stack[50];
LL a[N];

void write(__int128 x)
{
	int top=0;
	while (x) stack[++top]=x%10,x/=10;
	while (top) putchar(stack[top--]+'0');
	puts("");
}

void init()
{
	scanf("%d%d",&n,&ty);
	if (!ty)
	{
		for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	}
	else
	{
		int x,y,z,m;scanf("%d%d%d%d%d%d",&x,&y,&z,&b[1],&b[2],&m);
		for (int i=3;i<=n;i++) b[i]=((LL)x*b[i-1]+(LL)y*b[i-2]+(LL)z)%(1<<30);
		int ls=0;
		while (m--)
		{
			int p,l,r;scanf("%d%d%d",&p,&l,&r);
			for (;ls<p;ls++) a[ls+1]=b[ls+1]%(r-l+1)+l;
		}
	}
	for (int i=1;i<=n;i++) a[i]+=a[i-1];
}

LL get(int x)
{
	return a[x]*2-a[f[x]];
}

void solve()
{
	int h=1,t=1;
	for (int i=1;i<=n;i++)
	{
		while (h<t&&a[i]>=get(que[h+1])) h++;
		f[i]=que[h];
		while (t>=h&&get(que[t])>=get(i)) t--;
		que[++t]=i;
	}
	__int128 ans=0;
	for (int i=n;i;i=f[i]) ans+=(__int128)(a[i]-a[f[i]])*(a[i]-a[f[i]]);
	write(ans);
}

int main()
{
	init();
	solve();
	return 0;
}

樹的重心

考慮對每個點求出刪掉哪些邊後這個點會變成重心。對每棵子樹用一棵線段樹維護子樹中的sizesize。枚舉刪掉哪一棵子樹中的邊,討論刪邊後剩下的子樹中最大的是誰,在對應線段樹中查詢即可。
時間複雜度O(nlogn)O(n\log n).

#include<bits/stdc++.h>
#define pb push_back

typedef long long LL;

const int N=300005;

int n,size[N],dfn[N],mx[N],tim,sz,rt[N],root,bel[N];
LL ans;
struct tree{int s,l,r;}t[N*30];
std::vector<int> e[N];

void dfs(int x,int fa)
{
	size[x]=1;dfn[x]=++tim;bel[tim]=x;
	for (auto to:e[x])
		if (to!=fa) dfs(to,x),size[x]+=size[to];
	mx[x]=tim;
}

int newnode()
{
	sz++;
	t[sz].s=t[sz].l=t[sz].r=0;
	return sz;
}

void ins1(int &d,int l,int r,int x)
{
	int p=d;d=newnode();t[d]=t[p];
	t[d].s++;
	if (l==r) return;
	int mid=(l+r)/2;
	if (x<=mid) ins1(t[d].l,l,mid,x);
	else ins1(t[d].r,mid+1,r,x);
}

void ins2(int &d,int l,int r,int x,int y)
{
	if (!d) d=newnode();
	t[d].s+=y;
	if (l==r) return;
	int mid=(l+r)/2;
	if (x<=mid) ins2(t[d].l,l,mid,x,y);
	else ins2(t[d].r,mid+1,r,x,y);
}

int query(int d,int p,int l,int r,int x,int y)
{
	if (x>y||y<l||x>r) return 0;
	if (x<=l&&r<=y) return t[d].s-t[p].s;
	int mid=(l+r)/2;
	return query(t[d].l,t[p].l,l,mid,x,y)+query(t[d].r,t[p].r,mid+1,r,x,y);
}

int query(int d,int p,int q,int l,int r,int x,int y)
{
	if (x>y||y<l||x>r) return 0;
	if (x<=l&&r<=y) return t[d].s+t[p].s-t[q].s;
	int mid=(l+r)/2;
	return query(t[d].l,t[p].l,t[q].l,l,mid,x,y)+query(t[d].r,t[p].r,t[q].r,mid+1,r,x,y);
}

void solve(int x,int fa)
{
	int mx1=n-size[x],mx2=0;
	for (auto to:e[x])
	{
		if (to==fa) continue;
		if (size[to]>mx1) mx2=mx1,mx1=size[to];
		else mx2=std::max(mx2,size[to]);
	}
	int sum=0;
	for (auto to:e[x])
	{
		if (to==fa) continue;
		if (size[to]==mx1)
		{
			int l=size[to]*2-n,r=size[to]-mx2;
			sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
			l=size[to]-mx2+1,r=n-mx2*2;
			sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
		}
		else
		{
			int l=1,r=n-mx1*2;
			sum+=query(rt[mx[to]],rt[dfn[to]-1],1,n,l,r);
		}
	}
	if (n-size[x]==mx1)
	{
		int l=(n-size[x])*2-n,r=n-size[x]-mx2;
		sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
		l=n-size[x]-mx2+1,r=n-mx2*2;
		sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
	}
	else
	{
		int l=1,r=n-mx1*2;
		sum+=query(root,rt[dfn[x]],rt[mx[x]],1,n,l,r);
	}
	ans+=(LL)sum*x;
	for (auto to:e[x])
	{
		if (to==fa) continue;
		ins2(root,1,n,size[to],-1);
		ins2(root,1,n,n-size[to],1);
		solve(to,x);
		ins2(root,1,n,n-size[to],-1);
		ins2(root,1,n,size[to],1);
	}
}

void clear()
{
	sz=root=tim=0;ans=0;
	for (int i=1;i<=n;i++) e[i].clear(),rt[i]=0;
}

int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		for (int i=1;i<n;i++)
		{
			int x,y;scanf("%d%d",&x,&y);
			e[x].pb(y);e[y].pb(x);
		}
		dfs(1,0);
		for (int i=1;i<=n;i++) rt[i]=rt[i-1],ins1(rt[i],1,n,size[bel[i]]);
		for (int i=2;i<=n;i++) ins2(root,1,n,size[i],1);
		solve(1,0);
		printf("%lld\n",ans);
		clear();
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章