题解
难度
A B C D E N 难度1
G H J L 难度2
F M O 难度3
I K 难度4
Problem D
当某个尾号的出现次数大于k时,不可能。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll cnt[10000];
void solve() {
ll n,k;
cin>>n>>k;
for(ll i=1;i<=n;++i){
ll x;
cin>>x;
if (++cnt[x%10000] > k) {
cout<<"Which is my package???";
return;
}
}
cout<<"This is my package!!!";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem E
暴力匹配即可。这里给出手写暴力匹配的代码,也可以直接用库函数。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
string S[105], T[105];
void solve() {
// 暴力算法
ll n,m,ans=0;
cin>>n>>m;
for (ll i=1;i<=n;++i) cin>>S[i];
for (ll i=1;i<=m;++i) cin>>T[i];
for (ll i=1;i<=n;++i){
ll suc=0;
for (ll ii=1;ii<=m;++ii) {
string& s=S[i], &t=T[ii];
ll l1=s.length(),l2=t.length();
for(ll j=0;j<=l1-l2;++j){
// 当前位置是否匹配
ll ok=1;
for(ll k=0;k<l2;++k){
if(s[j+k]!=t[k]){
ok=0;
break;
}
}
if(ok){
suc=1;
break;
}
}
if(suc) break;
}
if (suc) ans+=i;
}
cout<<ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem F
令\(f=\frac{(i+1)^6-1}{i},g=\frac{(i+1)^5-1}{i}\),则\(B^{(i)}=(-i-1,f+i+1,-1,g+1)\)。
如果观察不出这个结果,可以把\(ad-bc=1,a+b=f,c+d=g\)方程中的\(b\)用\(a\)和\(f\)表示,\(d\)用\(g\)和\(c\)表示,代回方程得到\(ag-fc=1\),显然满足exgcd的形式。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1;
y = 0;
return;
}
ll x1, y1;
exgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
ll a,c;
cin>>n;
for(ll i=1+1;i<=n+1;++i){
ll f=(i*i*i*i*i*i-1)/(i-1), g=(i*i*i*i*i-1)/(i-1);
exgcd(g,f,a,c);
cout<<a<<" "<<f-a<<" "<<-c<<" "<<g+c<<"\n";
}
return 0;
}
Problem G
规则1每堆石子去掉一个后,剩下的能取\(\lfloor\frac{\sum{(a_i-1)}}{k}\rfloor\)次,规则2则是\(\sum \lfloor{\frac{a_i-1}{k}}\rfloor\)次,根据奇偶性判断即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
ll n,k;
cin>>n>>k;
ll sum=0, cnt=0;
for(ll i=1;i<=n;++i){
ll x;
cin>>x;
sum+=--x;
cnt+=x/k;
}
sum /= k;
if ((sum&1) || (cnt&1)) {
cout<<"Alice";
}
else{
cout<<"Bob";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem H
这里介绍\(O(r)\)做法。枚举因数\(p\),考虑\(p\)的倍数在\([l,r]\)内出现的次数,这个次数是\(\lfloor{\frac{r}{p}}\rfloor-\lceil{\frac{l}{p}}\rceil+1\),乘上\(p\)就是\(p\)对答案的贡献,累加\(p\)对答案的贡献即可。本题Hard Version进一步增加数据量,需要根号分治来实现\(O(\sqrt{r})\)的复杂度。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
ll l,r,ans=0;
cin>>l>>r;
for(ll p=1;p<=r;++p){
ans+=max(0LL, r/p-(l+p-1)/p+1)*p;
}
cout<<ans<<'\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem I
莫队算法维护区间最大出现次数。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N=1e5+5;
ll a[N];
ll id[N]; // 属于哪个块
ll bd;
ll n,q;
#define low(i) ((i-1)*bd+1)
#define high(i) (min(n,i*bd))
struct {
ll l, r, id;
} qs[N];
ll ans[N];
ll cnt[N], maxcnt[N];
ll pre[N];
ll tree[N<<2]; // 可以直接莫队维护区间最值
#define ls (p<<1)
#define rs (p<<1|1)
void build(ll p, ll pl, ll pr) {
if(pl==pr){
tree[p]=a[pl];
return;
}
ll mid=pl+pr>>1;
build(ls,pl,mid);
build(rs,mid+1,pr);
tree[p]=max(tree[ls],tree[rs]);
}
ll query(ll L, ll R, ll p, ll pl, ll pr){
if(pl>=L && pr<=R){
return tree[p];
}
ll mid=pl+pr>>1;
ll ret=0;
if(L<=mid) ret=query(L,R,ls,pl,mid);
if(R>mid) ret=max(ret,query(L,R,rs,mid+1,pr));
return ret;
}
void upd(ll& x, ll y, ll& mx){
// y \in {1, -1}
maxcnt[x]--;
x+=y;
maxcnt[x]++;
// 超过了原本的最大值
if(y>0 && x>mx) mx=x;
// 原本是唯一的最大值
else if(y<0 && x-y==mx && maxcnt[mx]==0) mx=x;
}
void solve() {
cin>>n>>q;
for(ll i=1;i<=n;++i) cin>>a[i], pre[i]=pre[i-1]+a[i];
build(1,1,n);
for(ll i=1;i<=q;++i){
cin>>qs[i].l>>qs[i].r;
qs[i].id=i;
}
bd=ceil(sqrt(n));
for(ll i=1;i<=bd;++i){
for(ll j=low(i);j<=high(i);++j){
id[j]=i;
}
}
sort(qs+1,qs+1+q,[&](auto x, auto y) {
if(id[x.l]!=id[y.l])
return id[x.l]<id[y.l];
return x.r<y.r;
});
ll l,r,cl=1,cr=0,mx=0;
// mx最大次数 maxcnt维护次数出现的次数
for(ll i=1;i<=q;++i){
l=qs[i].l,r=qs[i].r;
if(l==r){
ans[qs[i].id]=a[l]*2;
continue;
}
while(cl>l) upd(cnt[a[--cl]],1,mx);
while(cr<r) upd(cnt[a[++cr]],1,mx);
while(cl<l) upd(cnt[a[cl++]],-1,mx);
while(cr>r) upd(cnt[a[cr--]],-1,mx);
ans[qs[i].id]=mx*query(l,r,1,1,n)+pre[r]-pre[l-1];
}
for(ll i=1;i<=q;++i){
cout<<ans[i]<<'\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem J
枚举选出的3个标签,遍历一遍树取最大值。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll rnk[26];
ll ct[6];
const ll N=1e5+5;
vector<ll> adj[N];
ll n;
ll color[N], sp[N];
void dfs(ll x, ll tags, ll s, ll& maxv) {
ll cnt=__builtin_popcountll(tags & ct[color[x]]);
if(cnt>0) {
switch (sp[x]) {
case 'A':
s+=1;
break;
case 'B':
s*=2;
break;
case 'C':
s-=1;
break;
case 'D':
s=max(cnt,s);
}
}
for(ll& y:adj[x]){
dfs(y,tags,s,maxv);
}
if(adj[x].size()==0){
maxv=max(maxv,s);
}
}
void solve() {
cin>>n;
for(ll i=2;i<=n;++i){
ll f;
cin>>f;
adj[f].push_back(i);
}
for(ll i=1;i<=n;++i){
char p,q;
cin>>p>>q;
color[i]=rnk[p-'A'];
sp[i]=q;
}
ll ans=LLONG_MIN;
ll tag=0;
for(ll i=0;i<(1<<6);++i){
ll cnt=__builtin_popcountll(i);
if(cnt==3){
cnt=LLONG_MIN;
dfs(1,i,0,cnt);
if(cnt > ans) {
ans=cnt;
tag=i;
}
}
}
vector<ll> out;
ll j=1;
while(tag){
if(tag&1) out.push_back(j);
tag>>=1;
++j;
}
for(ll i=0;i<3;++i) cout<<out[i]<<' ';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
rnk['W'-'A']=0;
rnk['B'-'A']=1;
rnk['Y'-'A']=2;
rnk['R'-'A']=3;
rnk['P'-'A']=4;
rnk['G'-'A']=5;
ct[0]=0;
ct[1]=7; // 012
ct[2]=54; // 1245
ct[3]=57; // 0345
ct[4]=42; //135
ct[5]=20; //24
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem K
定义\(f[i][j]\)表示从\(i\)走到\(j\),形成回文路径的概率,那么枚举\(i\)的下一个点\(u\)和\(j\)和上一个点\(v\),则\(f[i][j]=\sum_u\sum_v P[i][u]*f[u][v]*P[v][j]\),其中\(P[i][j]\)是\(i\)的下一个点是\(j\)的概率。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N=105;
ll g[N][N], in[N], out[N];
string s;
ll n,m,S,T;
ll dp[N][N], vis[N][N], inv[300005];
const ll P=998244353;
ll qpow(ll x, ll y) {
ll ans=1;
while(y){
if(y&1) ans=ans*x%P;
y>>=1;
x=x*x%P;
}
return ans;
}
#define inc(x,y) (x=(x+((y)%P))%P)
ll dfs(ll x, ll y) {
if(vis[x][y])
return dp[x][y];
vis[x][y]=1;
if(x==y)
return dp[x][y]=1;
if(s[x]!=s[y])
return dp[x][y]=0;
for(ll i=1;i<=n;++i){
for(ll j=1;j<=n;++j){
if(g[x][i] && g[j][y] && s[i]==s[j]) {
if(i==y || j==x){
if(i==y && j==x) {
inc(dp[x][y], g[x][y]*inv[out[x]]);
}
}
else {
inc(dp[x][y], g[x][i]*g[j][y]%P*inv[out[x]]%P*inv[out[j]]%P*dfs(i,j));
}
}
}
}
return dp[x][y];
}
void solve() {
cin>>n>>m;
cin>>s;
s="$"+s;
for(ll i=1;i<=m;++i){
ll u,v;
cin>>u>>v;
g[u][v]++;
in[v]++, out[u]++;
}
for(ll i=1;i<=n;++i){
if(in[i]==0) S=i;
if(out[i]==0) T=i;
}
cout<<dfs(S,T);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
for(ll i=1;i<300005;++i){
inv[i]=qpow(i,P-2);
}
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem L
对于一个点P,向量AP与BP,BP与CP,CP与AP的叉积符号全部一样说明P在内部。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll x0_,y0_,x[4],y[4];
ll cross(ll a, ll b, ll c, ll d) {
return (a*d-b*c)>0?1:-1;
}
bool in(ll a, ll b, ll c) {
// a是否在bc范围内
if(b>c) swap(b,c);
return a>=b && a<=c;
}
bool solve() {
ll ok=0, sign=0;
for(ll i=1;i<=3;++i){
ll a=x[i],b=y[i],c=x[i%3+1],d=y[i%3+1];
// 判断在边上
if(((d-b)*(x0_-a)==(y0_-b)*(c-a) && in(x0_,a,c) && in(y0_,b,d)) || (x0_==a&&y0_==b)){
ok=1;
break;
}
// 判断叉乘符号
sign+=cross(a-x0_,b-y0_,c-x0_,d-y0_);
}
if(ok || abs(sign)==3) return true;
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>x[2]>>y[2]>>x[3]>>y[3];
for(ll i=0;i<=max(x[2],x[3]);++i){
for(ll j=0;j<=max(y[2],y[3]);++j){
x0_=i,y0_=j;
if(solve()){
cout<<'#';
}
else{
cout<<'.';
}
}
cout<<'\n';
}
return 0;
}
Problem M
先令\(x=y\),得到总和\(\sum w_i\),之后询问\(n-2\)条边,得到边权异或总和,这个结果再异或上第一步得到的总和得到边权,从而得到\(n-2\)条边的边权,剩下一条边用总和减掉其他边即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, u[100005], v[100005], ans[100005];
void solve() {
cin>>n;
ll w=0;
for(ll i=1;i<n;++i){
cin>>u[i]>>v[i];
}
cout<<"? "<<1<<" "<<1<<endl;
cin>>w;
for(ll i=1;i<n-1;++i){
cout<<"? "<<u[i]<<" "<<v[i]<<endl;
cin>>ans[i];
ans[i]^=w;
}
for(ll i=1;i<n-1;++i){
w-=ans[i];
}
ans[n-1]=w;
string o="! ";
for(ll i=1;i<n;++i){
o+=to_string(ans[i]);
if(i<n-1) o+=" ";
}
cout<<o<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem N
\(b_i=f_i-f_{i-1}\),\(c_i=\sum g_i\)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
ll n,m;
cin>>n>>m;
ll f=0,g=0,x;
for(ll i=1;i<=n;++i){
cin>>x;
cout<<x-f<<' ';
f=x;
}
cout<<'\n';
for(ll i=1;i<=m;++i){
cin>>x;
cout<<x+g<<' ';
g+=x;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}
Problem O
定义\(f[k][i][j]\)表示从\([1,i]\)选\(k\)个外卖,价格总和模\(t\)是\(j\)的方案中,美味值之和最大值。
则\(f[k][i][j]=\max\{f[k-1][i-1][(j-a[i]+t)\%t]+b[i],f[k][i-1][j]\}\),\(\max\)的第一项表示选第\(i\)个外卖,第二项表示不选。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n,t;
ll a[505], b[505], f[505][505][55];
void solve() {
cin>>n>>t;
for(ll i=1;i<=n;++i) cin>>a[i];
for(ll i=1;i<=n;++i) cin>>b[i];
for(ll i=1;i<=n;++i)
f[i][1][a[i]%t]=max(f[i][1][a[i]%t],b[i]);
for(ll i=2;i<=n;++i){
for(ll j=1;j<=n;++j){
for(ll k=0;k<t;++k){
f[i][j][k]=max(f[i-1][j][k],f[i][j][k]);
if(f[i-1][j-1][k])
f[i][j][(k+a[i])%t]=max(f[i-1][j-1][k]+b[i],f[i][j][(k+a[i])%t]);
}
}
}
for(ll k=1;k<=n;++k){
cout<<(f[n][k][0]==0 ? -1 : f[n][k][0])<<' ';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号