写在前面
笔者五月二十四日要参加26年CC南昌邀请赛,为练题拉了八套前两年的CC邀请赛铜牌题及以下,计划在比赛前把这些题做完并补题,目前是第一篇,25CCPC福建邀请赛,四题铜六题银,自估难度M<G<K<J,M秒过,G是能写但是码的时候有很多边界问题,最后还需要一个前缀和优化,因为太长时间没写题了,所有这道题也不顺利,K题是一打眼就没什么思路,看完题解理解了,但是还是不会写,J看完题解也不是很理解,不会做,这场计划补一下G、K、J
G
题面:

**题意解释:**这个e不重要,可以看到只需要加减就行了。题目要求给定区间的第一天,然后算一直到区间最后一天最大收益,每一次都是独立计算
**思路:**很明显,这个需要找的是连续非递减序列,然后用序列中的最后一个减去第一个,一直这样算直到序列最后一天,就是答案
如果纯暴力时间复杂度是O(n*m),会t,所以需要用一个前缀和来优化上升序列的差值,而且还要注意到收益需要加上第一天的鸡债
前缀和计算上升序列差值就是,使用一个数组存max(0,下一个数减去上一个数的差),然后用前缀和存所有的差值之和,这样就存下了任意一段序列的上升段最后一个减去第一个
代码:
cpp
void solve()
{
ll n,m;
cin>>n>>m;
vector<ll>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
ll k;
cin>>k;
vector<ll>temp(n+1);
temp[0]=0;
a[0]=0;
for(int i=1;i<=n;i++)
{
temp[i]=max(ll(0),a[i]-a[i-1]);
}
vector<ll>pre(n+1);
pre[0]=0;
for(int i=1;i<=n;i++)
{
pre[i]=pre[i-1]+temp[i];
}
while(m--)
{
ll s,t;
cin>>s>>t;
cout<<pre[t]-pre[s]+k<<endl;
}
}
K
题面:

**题意解释:**题意非常清晰,就是告诉你有一棵树,这棵树的边权就是两个点权相加,然后给定n-1条边权,但是不告诉具体点权,然后问有没有一种情况满足这些所有边权
**思路:**这个题我初步看是半点思路都没有的,根本想不到怎么做,然后看了题解,又理解了挺长时间,现在理解了。
可以设根节点点权为x,因为每个点都会直接或间接和根节点相连,所以每个点点权都可以用x表示,有可能是一个数+x,也有可能是一个数-x,这个怎么证明呢?当一个点与x直接相连时,肯定是w-x,然后另一个点与这个点相连的话,它就变成w1-(w-x),即w1-w+x,因为点权>0,所以可以求出x的范围是(w-w1)的最小值到w的最大值
**写一个笔者卡住的点:**我以为n-1条边没有把树定死,但是树的性质决定它只有n-1条边, 所以输入已经把树定死了,不需要担心有的边没给定边权,也不需要用根节点作为基数
如果这样这道题的难度就降低了,使用邻接表记录边权,然后DFS遍历每一个点,记录当前这个点相对x的正负和常数项,这样取max和min就得到结果了
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge
{
ll end;
ll w;
};
vector<edge>vec[200005];
ll minNum,maxNum;
vector<ll>coffe(200005),off(200005);
vector<bool>st(200005);
void dfs(ll cnt,ll fa,ll c,ll o)
{
st[cnt]=true;
coffe[cnt]=c;
off[cnt]=o;
for(auto &t:vec[cnt])
{
ll u=t.end;
ll w=t.w;
if(u==fa) continue;
ll nc=w-c;
ll no=-o;
dfs(u,cnt,nc,no);
}
}
void solve()
{
ll n;
cin>>n;
for(int i=0;i<n-1;i++)
{
ll u,v,w;
cin>>u>>v>>w;
vec[u].push_back({v,w});
vec[v].push_back({u,w});
}
dfs(1,-1,0,1);
ll L=-1e18;
ll R=1e18;
for(int i=1;i<=n;i++)
{
if(off[i]==1)
{
L=max(L,-coffe[i]+1);
}
else
{
R=min(R,coffe[i]-1);
}
}
if(R<L)
{
cout<<"NO"<<endl;
return;
}
ll x=L;
cout<<"YES"<<endl;
for(int i=1;i<=n;i++)
{
cout<<coffe[i]+off[i]*x<<" ";
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
ll t=1;
//cin>>t;
while(t--)
{
solve();
}
return 0;
}
J
题面:

**题意:**给定一个数,可以让他加上它的一个没用过的正约数,问如何在100次操作内加成一个完全平方数
**思路:**这道题题意很好理解,但是我做的时候是没什么思路的,值得高兴的是我两个队友似乎都会做。首先先看是不是已经满足了完全平方,如果满足就输出0;如果不满足再构造。
构造的时候要考虑什么方法是最简单的,题目没有要求变成最小的完全平方数,所以任意一个完全平方数都可以,我们可以试试二进制打法
构造成2^(2*i)的形式,这样的二进制形式就是1后跟着偶数个0,想将一个随机的数加成这样,可以每一次都加最右边的1后跟若干0的数,毫无疑问这个数必定是约数且不会重复,证明一下:从二进制拆一个最右边的1和后边的0出来,然后剩下的是他左边离他最近的1和后边一堆0,具体形式就是101011110000拆成101011100000+10000,左边这个长的肯定是右边这个的倍数,所以整个也是倍数。不重复是因为随着每一次加最右边的1,这样1会不断进位,1会越来越靠左且越来越少,所以x只会越来越大,所以这样可以得到结果
但是实测的时候,如果先判断是不是完全平方数,会超时,所以直接通用一种构造即可
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll fun(ll t)
{
ll sum=1;
while(t--) sum*=2;
return sum;
}
ll lowbit(ll x)
{
return x & -x;
}
void solve()
{
ll n;
cin>>n;
vector<ll>ans;
while(true)
{
ans.push_back(lowbit(n));
n+=lowbit(n);
if(n==lowbit(n)) break;
}
ll c=0;
ll temp=n;
while(n!=0)
{
n/=2;
c++;
}
if(c%2==0)
{
cout<<ans.size()+1<<endl;
for(int i=0;i<ans.size();i++) cout<<ans[i]<<" ";
cout<<temp<<endl;
}
else
{
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++) cout<<ans[i]<<" ";
cout<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
ll t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
篇末总结
其实这场比赛拿铜牌难度不大,只不过我太长时间没写题,一个基础的二进制、DFS都写不明白了,接下来再做25年郑州邀请赛