目录
前言
比赛地址:https://www.codechef.com/APRIL19B
CodeChef是世界著名的算法竟赛网站,而它每个月的举办的long challenge contest以有偿付费出题的高质量而闻名。比赛为期十天,期间要独立解决8个左右的由易到难的算法编程题目,严禁作弊行为。比赛分为div1和div2,前者的难度很大,参加的要求是rating>=1800,通常参加人数不超过千人,后者的难度较之相对容易,参加人数通过超过一万,但是两者会有一些问题是公共的。比赛结束后,所有代码公开,任何人都可以查看和学习,同时讨论区也会发布详细的英文题解。
这篇题解仅包括div2的七道题目,除去最后一道特殊的交互题。
因为我知识水平有限,如有错误或者纰漏,还望指出,如有疑问,也欢迎交流。
QQ:1434287907
Maximum Remaining
题意
给你一个大小为 的序列 ,求 %
数据范围
题解
显然是取最大的两个不同的数, ,答案就是 %
值得注意的是,当所有数相同时,答案为
代码
int main()
{
int n;
cin>>n;
vector<int> a(n);
for(int i=0; i<n; i++) cin>>a[i];
sort(a.rbegin(),a.rend());
for(int i=1; i<n; i++)
if(a[i]!=a[i-1]) return cout<<a[i],0;
cout<<0;
return 0;
}
Friend or Girlfriend
题意
给你一个长度为 的字符串 和一个字符 ,求有多少个包含 的不同子串
数据范围
题解
从后往前遍历字符串,维护在字符串中最早出现的 的下标 ,那么对于字符串中的每一个字符, 到 这段字符串中,包含 的子串数量即为
代码
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
cin>>s>>c;
ll ans=0;
for(int i=n-1,t=-1; ~i; i--)
{
if(s[i]==c) t=i;
if(~t) ans+=n-t;
}
cout<<ans<<endl;
}
return 0;
}
Fencing
题意
给以一个 大小的菜地,有 个菜地格子里种了菜,它们的坐标为 ,剩下的格子全部是杂草,让你用围栏把所有中了菜的格子围起来,求最小的围栏长度。
数据范围
题解
基于 的
代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> pi;
typedef vector<pi> vpi;
const int N=1000007;
int n,m,k,ans;
map<pi,bool> f,vis;
void dfs(pi t)
{
vis[t]=1;
if(!f[{t.fi+1,t.se}]) ans++;
else if(!vis[{t.fi+1,t.se}]) dfs({t.fi+1,t.se});
if(!f[{t.fi-1,t.se}]) ans++;
else if(!vis[{t.fi-1,t.se}]) dfs({t.fi-1,t.se});
if(!f[{t.fi,t.se+1}]) ans++;
else if(!vis[{t.fi,t.se+1}]) dfs({t.fi,t.se+1});
if(!f[{t.fi,t.se-1}]) ans++;
else if(!vis[{t.fi,t.se-1}]) dfs({t.fi,t.se-1});
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n>>m>>k;
f.clear(),vis.clear(),ans=0;
vpi p(k);
for(int i=0; i<k; i++) cin>>p[i].fi>>p[i].se,f[p[i]]=1;
for(int i=0; i<k; i++) if(!vis[p[i]]) dfs(p[i]);
cout<<ans<<endl;
}
return 0;
}
Subtree Removal
题意
给定 个节点的有根树(节点编号为 ),根节点编号为 ,每个节点都有点权 。
你可以任意次(包括零次)进行下面的操作:选择树中的某个节点,并删去包括该节点在内的整棵子树。
记收益为树中剩下的节点的点权之和减去 ,其中 代表操作次数。请求出最大收益。
数据范围
题解
树DP
对于每一个节点,若它所有的子节点的子树的最大收益之和 大于 ,则这个节点的子树的最大收益为 ,否则就要删除这颗子树,即这个节点的子树的最大收益为 。
代码
#include<bits/stdc++.h>
using namespace std;
#define eb emplace_back
typedef long long ll;
typedef vector<int> vi;
const int N=100007;
int n;
ll x;
ll a[N];
vi g[N];
ll solve(int u, int fa)
{
ll sum=a[u];
for(auto v: g[u])
{
if(v==fa) continue;
sum+=solve(v,u);
}
return max(sum,-x);
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n>>x;
for(int i=1; i<=n; i++) cin>>a[i],g[i].clear();
for(int i=1; i<n; i++)
{
int u,v;
cin>>u>>v;
g[u].eb(v);
g[v].eb(u);
}
cout<<solve(1,0)<<endl;
}
return 0;
}
Playing with Numbers
题意
给定 个节点的有根树(节点编号为 ),根节点编号为 。每个节点 拥有点权 和另一个参数 。
没有儿子的节点被称作叶子节点。记树中叶子节点的个数为 ,编号按升序排列为 。
我们按照如下方法定义叶子节点 的答案:
• 对于从根节点到 的路径上的每个节点,选择一个非负整数,乘以节点的点权。
• 对路径上所有节点按照上述方式算出来的值求和。
• 叶子 的答案 就是和对 取模的最大值。
请求出每个叶子节点的答案。
数据范围
题解
拓展贝祖(裴蜀)定理
贝祖定理:二元一次不定方程 存在整数解的充分必要条件是
扩展贝祖定理:改成 元一次不定方程,结论依然成立。
表示从根节点到节点 的 ,那么对于每一个叶子节点来说,它的答案就是
至于为什么,可以感性的理解一下
代码
#include<bits/stdc++.h>
using namespace std;
#define eb emplace_back
typedef long long ll;
typedef vector<int> vi;
const int N=100007;
int n;
ll v[N],m[N],dp[N];
bool vis[N];
vi g[N];
void solve(int u, int fa, ll t)
{
dp[u]=t;
bool ok=1;
for(auto x: g[u])
{
if(x!=fa)
{
solve(x,u,__gcd(dp[u],v[x]));
ok=0;
}
}
if(ok) vis[u]=1;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
memset(vis,0,sizeof vis);
for(int i=1; i<=n; i++) g[i].clear();
for(int i=1; i<n; i++)
{
int u,v;
cin>>u>>v;
g[u].eb(v);
g[v].eb(u);
}
for(int i=1; i<=n; i++) cin>>v[i];
for(int i=1; i<=n; i++) cin>>m[i];
solve(1,0,v[1]);
for(int i=2; i<=n; i++)
if(vis[i])
cout<<m[i]-__gcd(m[i],dp[i])<<' ';
cout<<endl;
}
return 0;
}
Kira Loves Palindromes
题意
给定字符串 ,请求出有多少种方案可以从该字符串中选出两个不相交的非空子串 和 ,使得其串接 为回文串。
数据范围
题解
表示 时选的第一个字串开头为 ,选的第二个字串以 为结尾, 到 这段字符串中符合题目要求的答案
转移方程为:
表示 到 的字符串中以 开头的回文串个数
表示 到 的字符串中以 结尾的回文串个数
所有 之和就是答案
代码
#include<bits/stdc++.h>
using namespace std;
#define eb emplace_back
typedef long long ll;
const int N=1007;
int n;
ll ans;
bool ok[N][N];
int f[N][N],b[N][N];
ll dp[N][N];
char s[N];
void pre()
{
for(int i=n-1; i>=0; i--)
for(int j=i; j<n; j++)
ok[i][j]=((s[i]==s[j])&&(j-i<3||ok[i+1][j-1]));
for(int i=0; i<n; i++) f[i][i]=b[i][i]=1;
for(int i=0; i<n; i++)
for(int j=i+1; j<n; j++)
{
if(ok[i][j]) f[i][j]=f[i][j-1]+1;
else f[i][j]=f[i][j-1];
}
for(int j=n-1; j>=0; j--)
for(int i=j-1; i>=0; i--)
{
if(ok[i][j]) b[i][j]=b[i+1][j]+1;
else b[i][j]=b[i+1][j];
}
}
void solve()
{
if(n>1) for(int i=0; i+1<n; i++) if(s[i]==s[i+1]) dp[i][i+1]=1,ans++;
for(int l=3; l<=n; l++)
for(int i=0; i+l-1<n; i++)
{
int j=i+l-1;
if(s[i]==s[j])
{
dp[i][j]=f[i+1][j-1]+b[i+1][j-1]+dp[i+1][j-1]+1;
ans+=dp[i][j];
}
}
}
int main()
{
cin>>s;
n=strlen(s);
pre();
solve();
cout<<ans;
return 0;
}
Mininum XOR over Tree
题意
给定 个节点的有根树(节点编号为 ),根节点编号为 。每个节点都有点权 。
你需要回答 Q 个询问。询问必须在线回答,即:只有回答了上一个询问,才能知道下一个询问。
每个询问中给定节点 和参数 。对于 的子树(包括 )中的所有节点 ,求 的最大值(⊕ 代表按位异或操作)。此外,求出能达到这一最大值的最小节点编号
数据范围
题解
可持久化01字典树合并
表示字典树节点编号为 表示的点权的最小编号
代码
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define lc (rt<<1)
#define rc (rt<<1|1)
#define mp make_pair
#define ep emplace_pop
#define eb emplace_back
#define sz(x) (int)(x).size()
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define mst(a,v) memset(a,v,sizeof(a))
#define debug(x) cout<<#x": "<<x<<'\n';
#define IOS {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);}
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pi;
typedef pair<ll,ll> pl;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<ld> vd;
typedef vector<pi> vpi;
typedef vector<pl> vpl;
const int K=20;
const int N=200007;
const int M=1111111;
const int mod=1e9+7;
const ld PI=acos(-1.0);
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3fLL;
template<class T> bool re(T &x)
{
int f=1;
char c=getchar();
for( ; !isdigit(c); c=getchar())
{
if(c==-1) return false;
if(c==45) f=-1;
}
for(x=c^48; isdigit(c=getchar()); x=(x<<3)+(x<<1)+(c^48));
return x*=f,true;
}
template<class T> inline void gmax(T &A,T B)
{
(A<B)&&(A=B);
}
template<class T> inline void gmin(T &A,T B)
{
(A>B)&&(A=B);
}
int n,m,u,x,tot,cnt;
int h[N],ne[N<<1],p[N<<1],w[N],tag[2*N*22],root[N],trie[2*N*22][2];
void add(int u, int v)
{
p[++tot]=v,ne[tot]=h[u],h[u]=tot;
}
int insert(int x, int k, int i)
{
int u=++cnt;
if(k<0)
{
tag[u]=i;
return u;
}
int t=(x>>k)&1;
trie[u][t]=insert(x,k-1,i);
trie[u][t^1]=0;
return u;
}
int merge(int x, int y, int k)
{
if(!x) return y;
if(!y) return x;
if(k<0) return (tag[x]<tag[y]? x:y);
int t=++cnt;
trie[t][1]=merge(trie[x][1],trie[y][1],k-1);
trie[t][0]=merge(trie[x][0],trie[y][0],k-1);
return t;
}
int query(int u, int x, int k)
{
if(k<0) return tag[u];
int t=(x>>k)&1;
if(trie[u][t^1]) return query(trie[u][t^1],x,k-1);
else return query(trie[u][t],x,k-1);
}
void dfs(int u, int fa)
{
for(int i=h[u]; i; i=ne[i]) if(p[i]!=fa) dfs(p[i],u);
for(int i=h[u]; i; i=ne[i]) if(p[i]!=fa) root[u]=merge(root[u],root[p[i]],K);
}
int main()
{
int T;
re(T);
while(T--)
{
re(n),re(m);
u=x=tot=cnt=0;
for(int i=1; i<=n; i++) h[i]=0;
for(int i=1; i<=n; i++)
{
re(w[i]);
root[i]=insert(w[i],K,i);
}
for(int i=1; i<n; i++)
{
int u,v;
re(u),re(v);
add(u,v),add(v,u);
}
dfs(1,0);
for(int i=0; i<m; i++)
{
int a,b;
re(a),re(b);
u^=a,x^=b;
u=query(root[u],x,K);
x=w[u]^x;
printf("%d %d\n",u,x);
}
}
return 0;
}