前言
我不想复习期末,我想加训qwq
一、A - First Contest of the Year
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int d,f;
cin>>d>>f;
int cur=f;
while(cur<=d)
{
cur+=7;
}
cout<<cur-d<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
根据题意,直接模拟即可。就是从当前天数f开始,每次加7直到超过一年中的d天。此时就是新一年的第一次比赛,所以把上一年的天数d减去就是新一年的第一次比赛。
二、B - Substring 2
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
cin>>n>>m;
string s,t;
cin>>s>>t;
s=" "+s;
t=" "+t;
int ans=INF;
for(int l=1;l<=n-m+1;l++)
{
int sum=0;
for(int i=1,j=l;i<=m;i++,j++)
{
int sc=s[j]-'0';
int tc=t[i]-'0';
sum+=(sc-tc+10)%10;
}
ans=min(ans,sum);
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为整个数据范围不大,所以还是可以直接暴力,那么就是枚举从s串的每个位置开始,计算让后续部分成为t串的代价,那么就是每次取余即可。
三、C - 1D puyopuyo
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
stack<int>stk;
for(int i=1;i<=n;i++)
{
stk.push(i);
vector<int>t;
while(t.size()<4&&!stk.empty())
{
t.push_back(stk.top());
stk.pop();
}
if(t.size()==4)
{
bool ok=true;
for(int i=0;i<4;i++)
{
if(a[t[i]]!=a[t[0]])
{
ok=false;
break;
}
}
if(ok)
{
continue;
}
}
while(!t.empty())
{
stk.push(t.back());
t.pop_back();
}
}
cout<<stk.size()<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题感觉见过好几次了,不管是在abc还是牛客还是cf,还是挺常见的。
对于这种拼接的问题,可以考虑用栈去模拟整个过程。那么就是每次考察栈里前三个字符,如果全一样就可以拼起来删除,否则就再压回去,等着后续去拼接即可。
四、D - Tail of Snake
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<ll>b(n+1);
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
vector<ll>c(n+1);
for(int i=1;i<=n;i++)
{
cin>>c[i];
}
vector<ll>best(n+1);
best[n]=0;
ll sum=c[n];
for(int i=n-1;i>=2;i--)
{
best[i]=b[i]+max(best[i+1],sum);
sum+=c[i];
}
ll pre=a[1];
ll ans=0;
for(int i=2;i<n;i++)
{
ans=max(ans,pre+best[i]);
pre+=a[i];
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
其实不难发现,这个题本质上就是让你选择两个划分点x和y,使得三部分的累加和最大,那么其实就是一个使得f(x,y)最大的问题。
对于求f(i,j)的问题,有一个很常见的trick,要么暴力n^2求,要么枚举i快速求j,要么计算贡献。在这个题里,很明显应该是需要枚举i,然后对于每个i快速求跟哪个j组合使得贡献最大。所以对于每个当前的i,需要构建出一个结构,可以做到快速查询当前位置i右侧选择哪个划分点j,使得中间部分b数组累加和和最后部分c数组累加和最大。那么就可以考虑定义best[i]表示从i位置开始往后,选择一个划分点j,使得b数组和c数组累加和最大。那么构建时就可以从后往前枚举,因为b数组至少需要有一个元素,所以对于当前位置i肯定需要选择b[i]。之后,每次考虑分直接在当前位置划分和不在当前位置划分两种情况讨论。若sum表示c数组的后缀和,那么依赖就是best[i+1]和sum的最大值。
构建完以后,就可以考虑从前往后枚举i,若pre表示a数组的前缀和,那么就是统计pre+best[i]的最大值即可。
五、E - Heavy Buckets
该加训倍增了......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
const int MAXN=2e5+5;
const int LIMIT=31;
int n,q;
vector<int>a(MAXN);
vector<vector<int>>stjump(MAXN,vector<int>(LIMIT));
vector<vector<ll>>stsum(MAXN,vector<ll>(LIMIT));
void solve()
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
stjump[i][0]=a[i];
stsum[i][0]=i;
}
for(int p=1;p<=30;p++)
{
for(int i=1;i<=n;i++)
{
stjump[i][p]=stjump[stjump[i][p-1]][p-1];
stsum[i][p]=stsum[i][p-1]+stsum[stjump[i][p-1]][p-1];
}
}
int t,x;
while(q--)
{
cin>>t>>x;
ll ans=0;
for(int p=30;p>=0;p--)
{
if((t>>p)&1)
{
ans+=stsum[x][p];
x=stjump[x][p];
}
}
cout<<ans<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这个题的建模其实真不难,感觉赛时也就一眼的事,但之后的维护和求解就是个问题了......
举个例子玩一下就可以发现,如果连一条从i到a[i]的边,那么就会形成若干棵基环树。那么不管从哪开始,若干轮之后肯定就是在环上转圈了。之后,对于快速查出走了多少步后到了哪以及收益多少的问题,就可以考虑去倍增构建st表。那么直接初始化stjump[i][0]为a[i],stsum[i][0]为i,然后去倍增,每次从大到小对t拆位计算即可。同时因为是倍增,所以其实过程中根本不用考虑环的问题,根据上一步的位置直接依赖了。
六、F - Sum of Mex
太变态了这个题......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
const int MAXN=2e5+5;
const int LIMIT=20;
vector<int>deep(MAXN);
vector<vector<int>>stjump(MAXN,vector<int>(LIMIT));
vector<int>sz(MAXN);
int n;
vector<vector<int>>g(MAXN);
int power;
int log2()
{
power=0;
while((1<<power)<=(n>>1))
{
power++;
}
return power;
}
void dfs(int u,int fa)
{
deep[u]=deep[fa]+1;
stjump[u][0]=fa;
for(int p=1;(1<<p)<=deep[u];p++)
{
stjump[u][p]=stjump[stjump[u][p-1]][p-1];
}
sz[u]=1;
for(int v:g[u])
{
if(v!=fa)
{
dfs(v,u);
sz[u]+=sz[v];
}
}
}
int LCA(int a,int b)
{
//让a是更深的节点
if(deep[a]<deep[b])
{
swap(a,b);
}
for(int p=power;p>=0;p--)
{
if(deep[stjump[a][p]]>=deep[b])
{
a=stjump[a][p];
}
}
if(a==b)
{
return a;
}
for(int p=power;p>=0;p--)
{
if(stjump[a][p]!=stjump[b][p])
{
a=stjump[a][p];
b=stjump[b][p];
}
}
return stjump[a][0];
}
int subtree(int u,int v)
{
int len=deep[u]-deep[v]-1;
for(int p=0;p<=power;p++)
{
if((len>>p)&1)
{
u=stjump[u][p];
}
}
return u;
}
void solve()
{
cin>>n;
for(int i=1,u,v;i<=n-1;i++)
{
cin>>u>>v;
u++,v++;
g[u].push_back(v);
g[v].push_back(u);
}
//trick:对于求f(i,j)的问题,要么暴力n^2求,要么枚举i快速求j,要么计算贡献
//这道题考虑计算贡献的方法
//因为是mex,所以对于f(i,j)=k的所有路径,其必然经过0~k-1中的所有点
//对于路径(i,j),从0开始枚举,当发现第一个没经过的点k时,其mex就已经确定了
//此时可以统计出所有终止在0的个数c[1],在1的个数c[2],在2的个数c[3]......
//那么答案就是1*c[0]+2*c[1]+3*c[3]+......
//又因为在枚举的过程中,每经过一个检查点,f(i,j)都会+1,所以在经过的时候可以直接产生1点贡献
//那么就只需要统计出经过0的个数k[1],经过1的个数k[2],经过2的个数k[3]......
//所以答案就是k[1]+k[2]+k[3]+......
log2();
dfs(n,0);
//特判所有经过0单点的路径数
//0内部子树里的一点和0外部的一点形成路径
ll ans=1ll*sz[1]*(n-sz[1]+1);
//从0内部的两棵子树里任选两点,形成经过0的路径
int cnt=0;
for(auto v:g[1])
{
if(v!=stjump[1][0])
{
ans+=1ll*sz[v]*cnt;
cnt+=sz[v];
}
}
int U=1,V=1,W=1;
for(int i=2;i<=n;i++)
{
int lca=LCA(i,W);
bool ok=false;
//特判
if(i==2)
{
ok=true;
W=lca;
V=i;
//默认U的深度更大
if(deep[U]<deep[V])
{
swap(U,V);
}
}
else
{
//U-V(W)单链
if(V==W)
{
//状态为U-V-i(W),扩展
if(lca==i)
{
V=W=i;
ok=true;
}
else if(lca==W)
{
int a=LCA(U,i);
//状态为i-U-V(W),扩展
if(a==U)
{
U=i;
ok=true;
}
//状态为U-i-V,不动
else if(a==i)
{
ok=true;
}
//状态为U-W(V)-i,改成双链
else
{
V=i;
ok=true;
}
}
//状态为U-V-lca-i,改成双链
else
{
V=i,W=lca;
ok=true;
}
}
//U-W-V双链
else
{
//状态为U-i-W-V,不动
if(!ok&&lca==W&&LCA(U,i)==i)
{
ok=true;
}
//状态为i-U-W-V,扩展
if(!ok&&lca==W&&LCA(U,i)==U)
{
U=i;
ok=true;
}
//状态为U-W-i-V,不动
if(!ok&&lca==W&&LCA(V,i)==i)
{
ok=true;
}
//状态为U-W-V-i,扩展
if(!ok&&lca==W&&LCA(V,i)==V)
{
V=i;
ok=true;
}
}
}
//当前点无法构成路径
if(!ok)
{
break;
}
//双链
if(V!=W)
{
//所有合法的路径为
//从 U的子树 和 V的子树 中任选两个节点
ans+=1ll*sz[U]*sz[V];
}
//单链
else
{
//所有合法的路径为
//从 U的子树 和 V的外部+除了路径的所有子树 中任选两个点
//对于第二部分,可以转化为找路径上V下面的那个点的外部
ans+=1ll*sz[U]*(n-sz[subtree(U,V)]);
}
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
还是这个在D题里用到的trick,对于求f(i,j)的问题,要么暴力n^2求,要么枚举i快速求j,要么计算贡献。又因为这道题前两种可以发现不太可行,所以考虑计算贡献的方法。
因为是mex,所以对于f(i,j)=k的所有路径,其必然经过0~k-1中的所有点。对于路径(i,j),从0开始枚举,当发现第一个没经过的点k时,其mex就已经确定了。此时可以统计出所有终止在0的个数c[1],在1的个数c[2],在2的个数c[3]......,那么答案就是1*c[0]+2*c[1]+3*c[3]+......。又因为在枚举的过程中,每经过一个检查点,f(i,j)都会+1,所以在经过的时候可以直接产生1点贡献。那么就只需要统计出经过0的个数k[1],经过1的个数k[2],经过2的个数k[3]......,所以答案就是k[1]+k[2]+k[3]+......。
那么之后需要先特判经过节点0的路径条数,然后分许多种情况讨论,维护路径的两端点和lca,每次计算路径条数作为贡献。具体的不想多说了太麻烦了,都写注释里了......
总结
先期末,考完开始寒假加训!