2022 China Collegiate Programming Contest (CCPC) Guilin Site(持续更新)

发布时间 2023-10-14 20:27:59作者: 空気力学の詩

Preface

由于还有两周就要滚去打区域赛了,这周开始周末每天都训一场吧

这场总体来说打的还可以,虽然E题这个Easy从卡局卡到3h,但由于其它的题都是一遍过所以罚时还尚可跻进Au区

后面一个小时看徐神和祁神苦战K大分类讨论,虽然场下感觉摸了一个B的做法出来,但感觉实现还是太麻烦了就没写,最后K也是没写出来苦露西

由于明天早上9点要再打场VP,而且今晚在给学校趣味赛出题验题,所以就没补BK这两个题,过段时间有空补上吧


A. Lily

签到题,把两把不是L的位置都变成C即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n; char s[N];
int main()
{
	//freopen("A.in","r",stdin); freopen("A.out","w",stdout);
	RI i; for (scanf("%d%s",&n,s+1),i=1;i<=n;++i)
	if (s[i]=='.'&&s[i-1]!='L'&&s[i+1]!='L') s[i]='C';
	return printf("%s",s+1),0;
}

B. Code With No Forces

每个人拆成三个来状压,细节好多以后再来写


C. Array Concatenation

首先发现当接上这个序列的转置后,后面两种操作就本质相同了,我们可以直接递推求出贡献

然后把贡献的式子写一写会发现最后其实只有两种情况,即要么一直不转置,要么第一步就转置,取其中较大的那个输出即可

#include<bits/stdc++.h>
#define int int64_t
using namespace std;
const int MOD = 1e9+7;

const int N = 1e5+5;
int n, m, sum[N];

int powe(int x, int p){
	int res=1;
	while (p>0){if (p%2) res=res*x%MOD; p/=2, x=x*x%MOD;}	
	return res;
}

signed main(){
	scanf("%lld%lld", &n, &m);
	for (int i=1; i<=n; ++i){
		int a; scanf("%lld", &a);
		sum[i]=(sum[i-1]+a)%MOD;	
	}
	
	int ans1 = sum[n]*powe(2, m-1)%MOD;
	ans1 = ans1*(n*powe(2, m)%MOD + 1)%MOD;
	
	int ans2=0;
	int len=n, S=sum[n], T=0;
	for (int i=1; i<=n; ++i) T=(T+sum[i])%MOD;
	for (int i=1; i<=m; ++i){
		T = (T*2%MOD + S*len%MOD)%MOD;	
		S = S*2%MOD, len=len*2%MOD;
		//printf("i=%lld T=%lld\n", i, T);
	}
	ans2 = T;
	
	//printf("ans1=%lld ans2=%lld\n", ans1, ans2);
	printf("%lld\n", max(ans1, ans2));
	return 0;	
}

D. Alice's Dolls

好神的多项式,做不了一点


E. Draw a triangle

徐神被这题要折磨疯了,不禁让我想起今年ICPC网络赛第二场的那个K,直接把我们队干爆炸了

把式子写出来会发现最小的可能取值就是\(\gcd(x,y)\),然后求一组合法解就直接用exgcd跑一下即可

比赛的时候不知道怎么脑抽了一直卡着,但我一直在想别的题也没太管,最后能过就行

#include <bits/stdc++.h>

using llsi = long long signed int;
using ld = long double;
using std::abs;

llsi t, a, b, c, d;

void exgcd(llsi a, llsi b, llsi &x, llsi &y) {
	if(!b) x = 1, y = 0;
	else exgcd(b, a % b, y, x), y -= a / b * x;
}

int main() {
	// freopen("1.in", "r", stdin);
	std::ios::sync_with_stdio(false);
	std::cin >> t;
	while(t--) {
		std::cin >> a >> b >> c >> d;
		if(a == c) { std::cout << a + 1 << ' ' << b << char(10); continue; }
		if(b == d) { std::cout << a << ' ' << b + 1 << char(10); continue; }
		
		const llsi A = b - d, B = c - a, C = a * d - b * c;
		
		llsi x, y;
		exgcd(A, B, x, y);
		// const llsi gab = x * A + y * B;
		// std::cerr << "X = " << x << " Y = " << y << char(10);
		
		std::cout << a + x << ' ' << b + y << char(10);
	}
	return 0;
}

F. Union of Circular Sectors

扇形面积并,巨恶心的计算几何神题,直接给我们队两个几何之神干懵了


G. Group Homework

典中典之换根DP,闪总的最爱

这题首先要手玩发现两条路径要么完全不相交,要么只有一个公共点,否则我们可以交换配对情况来得到一组更大的解

考虑只有一个公共点的情况是很简单的,直接把到每个点的路径中前\(4\)大的找出来即可,当然这里复杂度不卡可以直接大力排序

而另一种情况我们可以枚举在哪条边断开,然后在两边的树中分别找直径即可

但我们换根DP求出来的只有强制经过某个点的最长路,但我们现在要求的是一个子树中的最大值,乍一看很不好处理

但仔细一想,我们可以直接定义状态\(F(x,y)\)表示在\(x\)的子树内(不走\(y\)这个点方向的边),能得到的最长路

不难发现有效的状态其实是\(O(n)\)的,那么直接记搜一下就好了

这里为了方便用了sortmap,实际可以做到完全线性

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int n,x,y,a[N],ans; vector <int> v[N]; vector <pi> l[N]; map <int,int> f[N];
inline void DFS1(CI now=1,CI fa=0)
{
	for (auto to:v[now]) if (to!=fa)
	DFS1(to,now),l[now].push_back(pi((l[to].empty()?0:l[to][0].fi)+a[to],to));
	sort(l[now].begin(),l[now].end(),greater <pi>());
}
inline void DFS2(CI now=1,CI fa=0,CI out=0)
{
	if (fa) l[now].push_back(pi(out+a[fa],fa)),sort(l[now].begin(),l[now].end(),greater <pi>());
	while (l[now].size()<4) l[now].push_back(pi(0,0));
	for (auto to:v[now]) if (to!=fa) DFS2(to,now,to!=l[now][0].se?l[now][0].fi:l[now][1].fi);
}
inline int F(CI now,CI fa)
{
	if (f[now].count(fa)) return f[now][fa];
	int p1=0; while (l[now][p1].se==fa) ++p1;
	int p2=p1+1; while (l[now][p2].se==fa) ++p2;
	int ret=l[now][p1].fi+l[now][p2].fi+a[now];
	for (auto to:v[now]) if (to!=fa) ret=max(ret,F(to,now));
	return f[now][fa]=ret;
}
int main()
{
	//freopen("G.in","r",stdin); freopen("G.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (DFS1(),DFS2(),i=1;i<=n;++i) ans=max(ans,l[i][0].fi+l[i][1].fi+l[i][2].fi+l[i][3].fi);
	for (i=1;i<=n;++i) for (auto j:v[i]) ans=max(ans,F(i,j)+F(j,i));
	return printf("%d",ans),0;
}

H. Hysteretic Racing

啥玩意根本没人过


I. Invincible Hotwheels

徐神最爱的字符串,但难度有点大写不了一点


J. Permutation Puzzle

很有意思的一个题,感觉随便rush了一个写法就过了

首先我们可以给所有值不确定的位置\(i\)搞出一个取值区间\([l_i,r_i]\)

\(l_i\)的求解就是正向拓扑排序一遍,每次更新\(u\to v\)的时候就令\(l_v=\max(l_v,l_u+1)\)\(r_i\)的情况同理,在反图上拓扑排序一遍即可

然后我们考虑现在的问题,给出\(n\)个区间和\(n\)个,要求在每个区间内选一个数,使得所有\([1,n]\)中的每个数都被选了恰好一次

这个问题有个经典的贪心做法,我们从小到大枚举每个数,在所有左端点小于它的区间中,我们找右端点最小的那个区间和它配对,不难发现这样一定是最优的

这道题和上面的问题看似有区别,因为它还附带了一些拓扑关系,在符合上述配对的条件下还要满足大小关系

但仔细一想会发现对于图中\(u\to v\)连接的两点,一定有\(l_u<l_v,r_u< r_v\),而上面的贪心跑出的结果天然满足拓扑序的性质,因此可以直接上

实现的话用个堆模拟一下即可,总复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
int t,n,m,p[N],x,y,deg_G[N],deg_R[N],l[N],r[N],ans[N]; vector <int> G[N],R[N]; vector <pi> o[N];
int main()
{
	//freopen("J.in","r",stdin); freopen("J.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
		scanf("%d",&p[i]),G[i].clear(),R[i].clear(),deg_G[i]=deg_R[i]=0;
		for (i=1;i<=m;++i)
		{
			scanf("%d%d",&x,&y);
			G[x].push_back(y); ++deg_G[y];
			R[y].push_back(x); ++deg_R[x];
		}
		queue <int> q; for (i=1;i<=n;++i) if (p[i]) l[i]=r[i]=p[i]; else l[i]=1,r[i]=n;
		for (i=1;i<=n;++i) if (!deg_G[i]) q.push(i);
		int cnt=0; while (!q.empty())
		{
			int now=q.front(); q.pop(); ++cnt;
			for (auto to:G[now]) if (l[to]=max(l[to],l[now]+1),!--deg_G[to]) q.push(to);
		}
		if (cnt!=n) { puts("-1"); continue; }
		for (i=1;i<=n;++i) if (!deg_R[i]) q.push(i);
		while (!q.empty())
		{
			int now=q.front(); q.pop();
			for (auto to:R[now]) if (r[to]=min(r[to],r[now]-1),!--deg_R[to]) q.push(to);
		}
		bool flag=1; for (i=1;i<=n;++i) if (l[i]>r[i]) flag=0;
		if (!flag) { puts("-1"); continue; }
		for (i=1;i<=n;++i) o[i].clear();
		for (i=1;i<=n;++i) o[l[i]].push_back(pi(r[i],i));
		priority_queue <pi,vector <pi>,greater <pi>> hp;
		for (i=1;i<=n;++i)
		{
			for (auto [pos,id]:o[i]) hp.push(pi(pos,id));
			if (hp.empty()||hp.top().fi<i) { flag=0; break; }
			ans[hp.top().se]=i; hp.pop();
		}
		if (!flag) { puts("-1"); continue; }
		for (i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	}
	return 0;
}

K. Barrel Theory

大力分类讨论,感觉我们队比赛时候写的做法挺对的但就是过不去,看了题解又只能怀疑人生,留着过两天补吧


L. Largest Unique Wins

我只能说出题人这个数据范围真是神来一笔,差点把我骗去想什么单纯形法这类的东西了

这题其实想通的话非常简单,直接先拉两个人出来以\(100\%\)的概率选\(m\),然后再拉两个人出来以\(100\%\)的概率选\(m-1\),一直往前放下去即可

此时手玩不难发现对于任意一个人都达到了纳什均衡态

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=20;
int n,m,l,c[N],a[N][N];
int main()
{
	//freopen("L.in","r",stdin); freopen("L.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&m),l=m,i=1;i<=n;++i)
	{
		if (l>1&&c[l]==2) --l; ++c[l]; a[i][l]=1; 
	}
	for (i=1;i<=n;++i) for (j=1;j<=m;++j) printf("%.10lf%c",(double)a[i][j]," \n"[j==m]);
	return 0;
}

M. Youth Finale

傻逼题,但是我脑抽了写复杂了

首先求出初始逆序对数,考虑每次shift操作带来的影响,不难发现其实就是看后面的数和\(p_1\)的大小关系

reverse操作就直接拿\(C_n^2\)减去当前答案即可,然后把原来在前面的shift操作改到后面即可

因此直接拿个deque模拟一下,比赛的时候脑抽了拿树状数组去维护了下后面的数,我的评价是脑子被门夹了

#include<cstdio>
#include<iostream>
#include<deque>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=600005;
int n,m,p[N],ret,all,rev; deque <int> dq; char s[N];
class Tree_Array
{
	private:
		int bit[N];
	public:
		#define lowbit(x) (x&-x)
		inline int get(RI x,int ret=0)
		{
			for (;x<=n;x+=lowbit(x)) ret+=bit[x]; return ret;
		}
		inline void add(RI x,CI y)
		{
			for (;x;x-=lowbit(x)) bit[x]+=y;
		}
		#undef lowbit
}A,B;
signed main()
{
	//freopen("M.in","r",stdin); freopen("M.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&m),all=n*(n-1)/2LL,i=1;i<=n;++i)
	scanf("%lld",&p[i]),ret+=A.get(p[i]),A.add(p[i],1),dq.push_back(p[i]);
	for (A.add(p[1],-1),i=1;i<n;++i) B.add(p[i],1);
	for (scanf("%s",s+1),printf("%lld\n",ret),i=1;i<=m;++i)
	{
		if (n==1) { putchar('0'); continue; }
		if (s[i]=='R') rev^=1; else
		{
			if (rev)
			{
				int grt=B.get(dq.back()); ret-=grt; ret+=n-1-grt;
				int ed=dq.back(); dq.pop_back(); int sed=dq.back();
				A.add(ed,-1); B.add(sed,-1); A.add(dq.front(),1); B.add(ed,1); dq.push_front(ed);
			} else
			{
				int grt=A.get(dq.front()); ret-=n-1-grt; ret+=grt;
				int fr=dq.front(); dq.pop_front(); int sfr=dq.front();
				A.add(sfr,-1); B.add(fr,-1); A.add(fr,1); B.add(dq.back(),1); dq.push_back(fr);
			}
		}
		putchar((rev?all-ret:ret)%10+'0');
	}
	return 0;
}

Postscript

唉明天还要早起打比赛,冲冲冲