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