2025CCPC福建邀请赛总结加补题

这场邀请赛在线上pta打,开局顺风顺水,甚至一度保持在前十的位置,可惜后继有些无力,hsk即使下午有大雾实验考试也要坚持参赛前两个小时真的很感动
最后由于pta查重,本来周六下午的比赛硬是拖到周二才出成绩,紧张了3天最后可惜遗憾银牌前几名(33名)

接下来是补题内容:
链接https://codeforces.com/gym/105977

M
我第一眼看的签到题,很快拿下了,太简单所以不讲思路了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
string s[12] = {
    " ",
    "FZU",
    "FNU",
    "FZU",
    "FZU",
    "FAFU",
    "HQU",
    "MJU",
    "XMUT",
    "QNU",
    "JMU",
    "FZU"
};
int main(){
    int n;cin>>n;
    cout<<s[n];
    return 0;
}

K
很简单的树形dp,假设1为根,深度为1,节点1权值为x,那么深度为2的点权就该是w-x,继续用深度为2的点推到深度为3的点点权,在树上dfs每个点i都有类似ai+(-)x的点权,而且在[1,1e9]范围内,解出是否存在x对每个方程有满足条件的解即可
时间复杂度\(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) = ((x)& - (x))
#define rep(a,b,c) for(int a=b;a<=c;a++)
#define per(a,b,c) for(int a=b;a>=c;a--)
#define x first
#define y second
using namespace std;
typedef long long ll;

const int N = 2e5+4;
const ll inf = 1e9;
struct edge{
	ll to,w;
}; 
vector<edge> e[N];
ll n,u,v,w;
ll dep[N],dp[N],ans[N];

void dfs(int x,int fa){
	dep[x] = dep[fa]+1;
	for(auto &i:e[x]){
		if(i.to!=fa){
			dp[i.to] = i.w-dp[x];
			dfs(i.to,x);
		}
	}
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	rep(i,1,n-1){
		cin>>u>>v>>w;
		e[u].emplace_back(edge{v,w});
		e[v].emplace_back(edge{u,w});
	}
	dfs(1,0);
	ll l = 1,r = 1e9;
	rep(i,2,n){
		if(dep[i]&1){
			l = max(l,1-dp[i]);
			r = min(r,inf-dp[i]);
		}
		else{
			l = max(l,dp[i]-inf);
			r = min(r,dp[i]-1);
		}
	}
	if(l<=r){
		cout<<"YES\n";
		cout<<l<<' ';
		rep(i,2,n){
			if(dep[i]&1)cout<<l+dp[i]<<' ';
			else cout<<dp[i]-l<<' ';
		}
	}
	else cout<<"NO\n";
	return 0;
}

G
对于每笔借债,可以单独算这笔钱在借到到还出过程中赚到的钱,由于一切都用指数表示,股票收益的乘法就变成了加法,考虑如何做到最大收益,如果第x+1天股票价格比第x天高,那么我们就在第x天直接all in,再在第x+1天全部卖出,收益是\(e^{k-a_x+a_{x+1}}\),e^k是本金,由此可知,假设一笔钱\(e^k\)第l天借入,第r天还出,这笔钱最后会变成\(e^{k+\sum a_j-a_i}\)其中i,j表示
区间[i,j]天里股票是单增且区间是极大的,做股票价格的差分数组d[],把其中负数值取为0,一笔借债就是对d[l]~d[r]的求和,用树状数组可以很简单维护
时间复杂度\(O(n+mlogn)\)

点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)& - (x))
#define rep(a,b,c) for(int a=b;a<=c;a++)
#define per(a,b,c) for(int a=b;a>=c;a--)
#define x first
#define y second
using namespace std;
typedef long long ll;

const int N = 1e5+5;
int n,m,k,s,t;
int a[N],tree[N];


int sum(int x){
	int ans = 0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

void update(int x,int d){
	while(x<=N){
		tree[x]+=d;
		x+=lowbit(x);
	}
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	rep(i,1,n)cin>>a[i];
	rep(i,2,n){
		if(a[i]-a[i-1]>0)update(i,a[i]-a[i-1]);
	}
	cin>>k;
	rep(i,1,m){
		cin>>s>>t;
		cout<<k-sum(s)+sum(t)<<'\n';
	}
	return 0;
}

I
题目不重复叙述了,要构造一个符合要求的树,注意到至少要有两个非割点,
且恰好只有两个非割点的时候是一条链,此时非割点只能是n-1和n
有两个以上非割点时,把所有割点连成头为1的一条链,再在尾部添加一个点n,其余非割点绕点1连成一个环(1-a1-a2-a3-……-1),此时1的度为3,n的度为1,其余点度为2,符合要求
时间复杂度O(n)

点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) = ((x)& - (x))
#define rep(a,b,c) for(int a=b;a<=c;a++)
#define per(a,b,c) for(int a=b;a>=c;a--)
#define x first
#define y second
using namespace std;
typedef long long ll;

const int N = 1e5+4;
int t,n,cnt;
string s;
pair<int,int> ans[N];

bool solve(){
	cnt = 0;
	int cnt0 = 0,cnt1 = 0;
	rep(i,2,n-1){
		if(s[i] == '0')++cnt0;
		else ++cnt1;
	}
	if(cnt0 == 0){
		cout<<"-1\n";
		return false;
	}
	else if(cnt0 == 1&&s[n-1]!='0'){
		cout<<"-1\n";
		return false;
	}
	int pre = 1;
	rep(i,2,n-1){
		if(s[i] == '1'){
			++cnt;
			ans[cnt].x = pre,ans[cnt].y = i;
			//cout<<pre<<' '<<i<<'\n';
			pre = i;
		}
	}
	++cnt;
	ans[cnt].x = pre,ans[cnt].y = n;
	//cout<<pre<<' '<<n<<'\n';
	if(cnt0 == 1){
		++cnt;
		ans[cnt].x = 1,ans[cnt].y = n-1;
		//cout<<1<<' '<<n-1<<'\n';
		return true;
	}
	else{
		pre = 1;
		rep(i,2,n-1){
			if(s[i] == '0'){
				++cnt;
				ans[cnt].x = pre,ans[cnt].y = i;
				//cout<<pre<<' '<<i<<'\n';
				pre = i;
			}
		}
		++cnt;
		ans[cnt].x = 1,ans[cnt].y = pre;
		//cout<<1<<' '<<pre<<'\n';
	}
	return true;
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--){
		cnt = 0;
		cin>>n;
		cin>>s;s = "  " + s;	
		if(solve()){
			cout<<cnt<<'\n';
			rep(i,1,cnt){
				cout<<ans[i].x<<' '<<ans[i].y<<'\n';
			}
		}
		
	}
	return 0;
}

其他题目都是队友打的,我写下大概思路
J
要使一个数x经过操作变成完全平方数,考虑把它变成2的偶数次幂,把x写成2进制,例如对一个数11001101,先加上约数1变成11001110再加10变11010000再加10000变11100000再加100000变100000000是完全平方数,每次操作把x变成x+lowbit(x)即可
代码不见了

E
队友马力深厚拿下这题
操作的本质是选定一个区间循环移动,问最优操作
考虑遍历区间右端点r,如何在[1,r-1]选择l使操作区间[l,r]后答案最大
把区间[l,r]操作后答案变化为区间里牌权值的交错和,用set维护交错和就能用lower_bound在logn时间内找到最优的l
总时间复杂度\(O(nlogn)\)

点击查看代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) = ((x)& - (x))
#define rep(a,b,c) for(int a=b;a<=c;a++)
#define per(a,b,c) for(int a=b;a>=c;a--)
#define x first
#define y second
using namespace std;
typedef long long ll;

ll fastPow(ll a, ll n, ll mod){
    ll ans = 1;
    a %= mod;
    while(n) {
        if(n & 1)   ans = (ans*a) % mod;
        a = (a*a) % mod;
        n >>= 1;
    }
    return ans;
}

int gcd(int a, int b) {
  while (b != 0) {
    int tmp = a;
    a = b;
    b = tmp % b;
  }
  return a;
}

/*int sum(int x){
	int ans = 0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

void update(int x,int d){
	while(x<=N){
		tree[x]+=d;
		x+=lowbit(x);
	}
}*/
const int N = 1e5+10;
const long long maxnnn = 1e16+10;
long long dp[N<<1];
int n;
int T;
long long pre[N<<1];
set<ll> s_1;
set<ll> s_0;
inline void init()
{
	s_1.clear();
	s_0.clear();
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--)
	{
		init();
		cin>>n;
		long long Sig=0;
		for(int i=1;i<=n*2;i++)
		{
			cin>>dp[i];
			Sig+=dp[i];
			if((i&1)==1)
				pre[i]=pre[i-1]+dp[i];
			else 
				pre[i]=pre[i-1]-dp[i];
		}
//		puts("PRE:");
//		for(int i=1;i<=n*2;i++) cout<<pre[i]<<" ";puts(""); 
		long long now = pre[n*2];
		long long ans = abs(now);//long long abs?
		s_0.insert(0);
		s_0.insert(maxnnn);
		s_1.insert(maxnnn);
		for(int i=2;i<=n*2;i++)
		{
			long long tmp = now-pre[i]*2;
			long long TMP = now-pre[i]*2;
			tmp = tmp * -1;
//			cout<<tmp<<"|";	
			long long minn_tmp = maxnnn;
			long long maxn_tmp = maxnnn;
			if((i%2)==0)
			{
				if(s_0.lower_bound(tmp/2) != s_0.end())
				{
					minn_tmp = *s_0.lower_bound(tmp/2);
					if(s_0.lower_bound(tmp/2) != s_0.begin())
					{
						maxn_tmp = *(--s_0.lower_bound(tmp/2)  );
					}
				}
				s_1.insert(pre[i-1]);
			}
			else
			{
				if(s_1.lower_bound(tmp/2) != s_1.end())
				{
					minn_tmp = *s_1.lower_bound(tmp/2);
					if(s_1.lower_bound(tmp/2) != s_1.begin())
					{
						maxn_tmp = *(--s_1.upper_bound(tmp/2)  );
					}
				}
				s_0.insert(pre[i-1]);
			}
//			cout<<"TMP: tmp:"<<tmp<<" minn:"<<minn_tmp<<endl; 
			long long tmp1 = TMP + minn_tmp*2;
			long long tmp2 = TMP + maxn_tmp*2;
			tmp = min(abs(tmp1),abs(tmp2));
			ans = min(ans, tmp);

		}
//		if(((Sig-ans )% 2) != 0 ) puts("HEllo!");
		cout<<(Sig-ans) / 2<<endl;
//		cout<<Sig - (Sig-ans) / 2<<endl;
//		cout<<"debug: ans: "<<ans<<"Sig: "<<Sig<<endl;
	}
	return 0;
}

补题一个
C
二分答案,考虑如何在O(n)时间内判断答案是否大于或小于mid
经典想法,把数组a中小于mid的标为0,大于等于mid的标为1
贪心地想,要把a中数字0消除尽可能多,留下1尽可能多
对于连续的3个及以上的0,是可以一直消到只剩两个0或一个0的
考虑2个0或1个0时,00100是可以消到一个0的,其余情况不管怎么消除去掉的0和1数量都是相等的(或者消掉1比0多,这不符合我们的贪心原则)
因此可以用栈来维护,消除连续的3个及以上的0或00100,最后看栈里剩的0和1数量,1数量多于0则ans>=mid,反之ans<mid
总时间复杂度O(nlogn)

L
队友打的,没听讨论不太懂思路,之后再补

posted @ 2025-07-01 21:08  明天能下雨吗  阅读(542)  评论(0)    收藏  举报