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
队友打的,没听讨论不太懂思路,之后再补

浙公网安备 33010602011771号