2022 China Collegiate Programming Contest (CCPC) Guilin Site

 tot:vp写了三题罚时251,第四题C题推式子半天,都没有发现越来越偏离原题意了,然后一直在还一直在推,赛后才发现已经偏离题意了(有些时候还是要回头看看题意呀),然后赛后用同样的推法推了会就过了。然后第六题L是一个什么鬼纳什均衡,题目都读不懂在干什么,不补了。第七题J是一个拓扑序预处理一些信息,然后dp,不懂怎么预处理,也不懂怎么d,不补了。。共五题,铜牌区,快一些是银牌区(十几个吧)。这场铜尾是4题罚时638..可惜第四题题意歪了,不然完全能推出来。

A - Lily

思路:签到。

ll n;
void solve(){
	string s; cin >> n >> s;
	s="."+s+".";
	for(int i=1;i<=n;i++) if(s[i-1]!='L' && s[i]!='L' && s[i+1]!='L') s[i]='C';
	for(int i=1;i<=n;i++) cout << s[i];
}

M - Youth Finale

思路:队友写的。

ll n,m;
int lowbit(int x){return x&-x;}
int c[300005];
void update(int x,int k){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=k;
	}
}
int query(int x){
	int res=0;
	for(int i=x;i;i-=lowbit(i)){
		res+=c[i];
	}
	return res;
}
void solve(){
	cin>>n>>m;
	deque<int> dq;
	int ans=0;
	for(int i=1;i<=n;i++){
		int x; cin>>x;
		update(x,1);
		ans+=query(n)-query(x);
		dq.emplace_back(x);
	}
	cout<<ans<<endl;
	vector<int> vct;
	int head=1;
	while(m--){
		char x; cin>>x;	
		if(head){
			if(x=='S'){
				ans-=(dq.front()-1);
				ans+=n-dq.front();
				dq.emplace_back(dq.front());
				dq.pop_front();
				vct.emplace_back(ans%10);
			}
			else head^=1,ans=n*(n-1)/2-ans,vct.emplace_back(ans%10);
		}
		else{
			if(x=='S'){
				ans-=(dq.back()-1);
				ans+=n-dq.back();
				dq.emplace_front(dq.back());
				dq.pop_back();
				vct.emplace_back(ans%10);
			}
			else head^=1,ans=n*(n-1)/2-ans,vct.emplace_back(ans%10);
		}
	}	
	for(auto v:vct) cout<<v;
}

E - Draw a triangle

思路:队友写的。

ll n,m;
inline ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x=1,y=0; return a;}
	ll d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}
void solve(){
	ll x1,y1,x2,y2; cin >> x1 >> y1 >> x2 >> y2;
	if(x1==x2){
		cout << x1+1 << " " << y1 << "\n";
		return ;
	}
	if(y1==y2){
		cout << x1 << " " << y1+1 << "\n";
		return ;
	}
	if(x1>x2) swap(x1,x2),swap(y1,y2);
	ll a,b; a=max(abs(x1-x2),abs(y1-y2)),b=min(abs(x1-x2),abs(y1-y2));
//	for(int a=2;a<=500;a++){
//		for(int b=1;b<=800;b++) if(__gcd(a,b)==1){
//			ll mi=1e9,num=0;
//			while(num<=a*b){num+=b;
//				if(num%a==0) continue;
//				mi=min(num%a,mi);}
//			if(mi!=1){cout << "!!!\n\n\n";return ;}
//		}
//	}
	ll gc=__gcd(a,b);
	a/=gc,b/=gc;
	if(a==b){
		cout << x1-1 << " " << y1 << "\n";
		return ;
	}
	ll k=0,m=0;
	exgcd(b,-a,k,m);
//	cout << k << "\n";
//	cout << b*k << "\n";
	if(y1>y2){
		if(abs(x1-x2)>=abs(y1-y2)){
			if(b*k%a==1) cout << x1+k << " " << y1-b*k/a << "\n";
			else cout << x1+k << " " << y1-b*k/a-1 << "\n";
		}else{
			if(b*k%a==1) cout << x2-b*k/a << " " << y2+k << "\n";
			else cout << x2-b*k/a-1 << " " << y2+k << "\n";
		}
	}else{
		if(abs(x1-x2)>=abs(y1-y2)){
			if(b*k%a==1) cout << x2-k << " " << y2-b*k/a << "\n";
			else cout << x2-k << " " << y2-b*k/a-1 << "\n";
		}else{
			if(b*k%a==1) cout << x2-b*k/a << " " << y2-k << "\n";
			else cout << x2-b*k/a-1 << " " << y2-k << "\n";
		}
	}
}

C - Array Concatenation

思路:所谓的正解是发现只有答案只有两个,要么在第一次就翻转,要么就一直不翻转。但是感觉不如枚举reverse位置的思路(这个才是应该是正解),然后推出o(1)的公式,就能写了.取模非常的多,写的非常恶心,应该重载运算符自动取模的。

const int mod=1e9+7;
int quickpow(int a,int b){
    int res=1;
    while(b){
        if(b&1) res*=a,res%=mod;
        a*=a,a%=mod;
        b>>=1;
    }
    return res;
}
long long n,m;
vector<long long> a(100005,0);
int s1[100005],s2[100005];     //s1[i]定义为,用了i次时的ans
问题1:一开始没读歪题,后来手写样例写着写着拼数组一直在给后面拼原始数组,不是现在的完整数组...
问题2:取mod有一个地方少取了..检查了一次又一次,有个地方加了无意义的(),导致取模的时候没留意到括号里面少取模了..
所谓的正解是发现只有答案只有两个.
但是感觉不如这个枚举reverse位置的思路,这个才是正解,只要推出o(1)的公式,就能写了.
void solve(){
    cin>>n>>m;
    int suma=0,ansa=0,sumb=0,ansb=0;
    for(int i=1;i<=n;i++) cin>>a[i],suma+=a[i],suma%=mod,ansa+=suma,ansa%=mod;
    auto b=a;
    reverse(b.begin()+1,b.begin()+n+1);
    for(int i=1;i<=n;i++) sumb+=b[i],sumb%=mod,ansb+=sumb,ansb%=mod;
    int q=quickpow(2,mod-2);
    s1[0]=ansa,s2[0]=ansb;
    for(int i=1;i<=m;i++){
        s1[i]=(quickpow(2,i)-1+mod)%mod*(n+n*((quickpow(2,i)-1+mod)%mod)%mod)%mod*q%mod*suma%mod+quickpow(2,i)*ansa%mod,s1[i]%=mod;
        s2[i]=(quickpow(2,i)-1+mod)%mod*(n+n*((quickpow(2,i)-1+mod)%mod)%mod)%mod*q%mod*sumb%mod+quickpow(2,i)*ansb%mod,s2[i]%=mod;
    }
    int ans=s1[m];
    for(int i=0;i<m;i++){ //使用i次1操作之后,使用了reverse.
        int sum=suma*quickpow(2,i+1)%mod;
        int nn=n*quickpow(2,i+1)%mod;
        int ans0=(s1[i]+s2[i])%mod+nn*q%mod*sum%mod*q%mod;
        ans0%=mod;
        int move=m-i-1,res=0;
        res=(quickpow(2,move)-1+mod)%mod*(nn+nn*((quickpow(2,move)-1+mod)%mod)%mod)%mod*q%mod*sum%mod+quickpow(2,move)*ans0%mod,res%=mod;
        ans=max(ans,res);
    }
    cout<<ans;
}

G - Group Homework

思路:非常优秀的换根dp题。并且从

2022 China Collegiate Programming Contest (CCPC) Guilin Site(持续更新) - 空気力学の詩 - 博客园

2022 CCPC Guilin Site G - Luckyblock - 博客园

 两位大佬的博客中学到一种优秀的状态定义:

考虑记 F(u,v)表示在以 u 为根的树中,钦定删去邻接点 v 的子树时树的直径。

不妨对于 F(u,v),钦定 v 为 u 的父节点,考虑记忆化搜索。首先根据第一种情况预处理的 chain⁡(u),从中找出不以 v 为端点的最长和次长链更新经过该节点的路径的贡献,然后枚举其子节点考虑其子树中路径的贡献即可。

虽然 F 是一个二维的状态,但由于钦定了 (u,v)∈T 显然其中仅有 O(n) 级别个状态是有贡献的

typedef struct node{
    pair<int,int> a={0,0},b={0,0},c={0,0},d={0,0};
}node;
int n;
int arr[200005];
vector<int> vct[200005];
vector<pair<int,int>> edge;
vector<node> w(200005);
//f[u][v]的含义为,对于点u,不走v这个方向时,剩余的子树的带权直径--技巧,有效的状态会是o(n)的,记忆化搜索可以预处理出.
//不是预处理出,而是直接调用,最坏的情况不会超过o(n)此.
map<int,int> f[200005];  //数组开不了f[2e5][2e5]..  --unordered_map还慢了100ms.
void add(int idx,pair<int,int> x){
    if(x.first>=w[idx].a.first){
        w[idx].d=w[idx].c;
        w[idx].c=w[idx].b;
        w[idx].b=w[idx].a;
        w[idx].a=x;
    }
    else if(x.first>=w[idx].b.first){
        w[idx].d=w[idx].c;
        w[idx].c=w[idx].b;
        w[idx].b=x;
    }
    else if(x.first>=w[idx].c.first){
        w[idx].d=w[idx].c;
        w[idx].c=x;
    }
    else if(x.first>=w[idx].d.first){
        w[idx].d=x;
    }
}
pair<int,int> dfs1(int u,int fa){ //得到初步的w[]
    int res=0;
    for(auto v:vct[u]){
        if(v!=fa){
            pair<int,int> children=dfs1(v,u);
            res=max(res,children.first);
            add(u,children);
        }
    }
    return {res+arr[u],u};
}
void dfs2(int u,int fa){ //进一步得到w[]--每一个点的前4大.
    if(fa!=0){
        if(w[fa].a.second!=u) add(u,{w[fa].a.first+arr[fa],fa});
        else if(w[fa].b.second!=u) add(u,{w[fa].b.first+arr[fa],fa});
    }
    for(auto v:vct[u]) if(v!=fa) dfs2(v,u);
}
int cal(int u,int v){  //点u不走点v时的前两大的入权.
    //返回值都要加上arr[u]!
    if(w[u].a.second==v) return w[u].b.first+w[u].c.first+arr[u];
    else if(w[u].b.second==v) return w[u].a.first+w[u].c.first+arr[u];
    else if(w[u].c.second==v||w[u].d.second==v) return w[u].a.first+w[u].b.first+arr[u];
    return arr[u];
}
//一开始fa为0怎么处理出f[u][]的数据?
//solve:直接调用getDiameter!!而不是初始化之后再用f数组!!
//这是最关键的函数,而且要注意的是,记忆化之后,递归次数最多不会到o(n)次,后面的查询更是o(1)的.
//getDiameter(u,fa)可以求得,固定u,删去连向fa的边时,u子树的带权直径.
int getDiameter(int u,int fa){  //记忆化搜索处理出f[u][v]数组,o(n)--Noo!!
    if(f[u][fa]!=0) return f[u][fa];
    int res=cal(u,fa);
    for(auto v:vct[u]){
        if(v!=fa) res=max(res,getDiameter(v,u));
    }
    f[u][fa]=res;
    return f[u][fa];
}
//答案有两种可能:
//①是对于每个点,取前四大的孩子节点.
//②是对于每条边,两个端点取前二大的孩子节点,并且孩子不能是另一端点--这个想简单了,应该是端点两边的子树带权直径--key
void solve(){  //补G 换根dp & 空气力学の大佬的技巧
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i];
    for(int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        edge.emplace_back(u,v);
        vct[u].emplace_back(v);
        vct[v].emplace_back(u);
    }
    dfs1(1,0);  //得到初步的w[]
    dfs2(1,0);  //进一步得到w[]--每一个点的前4大.
//    getDiameter(1,0);  //记忆化搜索处理出f[u][v]数组,o(n)
    int ans=0;
    //每一个点取前四大.
    for(int i=1;i<=n;i++) ans=max(ans,w[i].a.first+w[i].b.first+w[i].c.first+w[i].d.first);
    //端点两边的子树带权直径.
    for(int i=1;i<=n;i++){
        for(auto v:vct[i]){
            ans=max(ans,getDiameter(v,i)+getDiameter(i,v));
        }
    }
    cout<<ans;
}
//参考题解:
//https://www.cnblogs.com/luckyblock/p/18124448
//https://www.cnblogs.com/cjjsb/p/17764670.html#g-group-homework
//hack:
//7
//5 5 5 1 4 4 4
//1 2
//2 3
//2 4
//4 5
//5 6
//5 7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值