Preface
久違地線下訓練,沒想到前年的比賽還有沒打過的漏網之魚
這場由於一箇中期題 G 被看出來是去年暑假前集訓做過的原,導致題目難度跨度有點大
最後一共出了 8 題,J 幾何的思路其實出的大差不差了,賽後改了改就過了
A. Modulo Ruins the Legend
首先轉化下題意,令 \(A=n,B=\frac{n\times (n+1)}{2},C=\sum_{i=1}^n a_i\),則題目要求一組 \(x,y\) 使得 \((Ax+By+C)\bmod m\) 的值最小
然後把這個問題扔給徐神,徐神自然而然地就解決了,真是太神奇了
#include <bits/stdc++.h>
using llsi = long long signed int;
llsi n, m;
std::tuple<llsi, llsi, llsi> exgcd(llsi a, llsi b) {
if(b == 0) return { a, 1, 0 };
auto [gcd, y, x] = exgcd(b, a % b);
y -= a / b * x;
return { gcd, x, y };
}
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n >> m;
llsi A = n, B = (n + 1) * n / 2 % m, C = 0;
if(B == 0) B = m;
for(int i = 0, a; i < n; ++i) std::cin >> a, C += a;
C %= m;
auto [gAm , x1, z1] = exgcd(A, m);
auto [gBm , y2, z2] = exgcd(B, m);
auto [gAll, xz, yz] = exgcd(gAm, gBm);
assert(xz * x1 * A + yz * y2 * B + (z1 * xz + z2 * yz) * m == gAll);
llsi k = C + (m - C) / gAll * gAll;
if(k < m) k += gAll;
k -= m;
std::cout << k << char(10);
llsi x = x1 % m * (xz % m) % m;
llsi y = y2 % m * (yz % m) % m;
assert((x * A + y * B - gAll) % m == 0);
x = x * (k - C) / gAll % m;
y = y * (k - C) / gAll % m;
assert((A * x + B * y - (k - C)) % m == 0);
if(x < 0) x += m;
if(y < 0) y += m;
std::cout << x << ' ' << y << char(10);
return 0;
}
C. No Bug No Game
首先如果 \(\sum_{i=1}^n p_i\le k\),則答案就是 \(\sum_{i=1}^n w_{i,p_i}\),否則一定存在某個數作為部分 Buff 進行貢獻
考慮直接列舉哪個數 \(i\) 作為部分貢獻,然後列舉它貢獻的值 \(t\in[0,\min(p_i,k)]\),則此時要在剩餘的數中選出一個 \(\sum p_i=k-t\),且 \(\sum w_{i,p_i}\) 最大的集合
每次重新跑個揹包複雜度肯定會寄,但由於只是少了一個物品,因此可以用經典的前字尾套路來最佳化
求出字首/字尾物品對應的揹包陣列,合併時由於只要知道某個體積的貢獻,複雜度是 \(O(k)\) 的,最後總複雜度就是 \(O(nkp)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=3005,INF=1e9;
int n,k,p[N],w[N][15],pre[N][N],suf[N][N];
int main()
{
scanf("%d%d",&n,&k); int sum=0,all=0;
for (RI i=1;i<=n;++i)
{
scanf("%d",&p[i]);
for (RI j=1;j<=p[i];++j)
scanf("%d",&w[i][j]);
sum+=p[i]; all+=w[i][p[i]];
}
if (sum<=k) return printf("%d",all),0;
for (RI j=1;j<=k;++j) pre[0][j]=suf[n+1][j]=-INF;
for (RI i=1;i<=n;++i)
{
for (RI j=0;j<=k;++j) pre[i][j]=pre[i-1][j];
for (RI j=p[i];j<=k;++j) pre[i][j]=max(pre[i][j],pre[i-1][j-p[i]]+w[i][p[i]]);
}
for (RI i=n;i>=1;--i)
{
for (RI j=0;j<=k;++j) suf[i][j]=suf[i+1][j];
for (RI j=p[i];j<=k;++j) suf[i][j]=max(suf[i][j],suf[i+1][j-p[i]]+w[i][p[i]]);
}
int ans=0;
for (RI i=1;i<=n;++i)
{
for (RI t=0;t<=min(p[i],k);++t)
for (RI j=0;j<=k-t;++j)
ans=max(ans,pre[i-1][j]+suf[i+1][k-t-j]+w[i][t]);
}
return printf("%d",ans),0;
}
D. Money Game
令 \(S=\sum_{i=1}^n a_i\),不難發現最後的穩定狀態一定為 \(a_1=\frac{2S}{n+1},a_2=a_3=\dots=a_n=\frac{S}{n+1}\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N];
int main()
{
scanf("%d",&n); long long sum=0;
for (RI i=1;i<=n;++i) scanf("%d",&a[i]),sum+=a[i];
printf("%.8lf ",2.0*sum/(n+1));
for (RI i=2;i<=n;++i) printf("%.8lf ",1.0*sum/(n+1));
return 0;
}
F. Da Mi Lao Shi Ai Kan De
簽到,按題意模擬即可
#include<cstdio>
#include<iostream>
#include<set>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
int n; cin>>n; set <string> ext;
for (RI i=1;i<=n;++i)
{
int m; cin>>m; bool ocr=0;
for (RI j=1;j<=m;++j)
{
string s; cin>>s; bool flag=0;
for (RI k=0;k+2<s.size();++k)
if (s[k]=='b'&&s[k+1]=='i'&&s[k+2]=='e') { flag=1; break; }
if (flag&&!ext.count(s)) ext.insert(s),ocr=1,cout<<s<<'\n';
}
if (!ocr) cout<<"Time to play Genshin Impact, Teacher Rice!\n";
}
return 0;
}
G. Subgraph Isomorphism
這題在 去年暑假前集訓圖論專題 裡出現過,這次就直接 CV 程式碼了
#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef vector <int> VI;
const int N=100005;
int T,n,m,x,y,pre[N],s,t,hsh[N],idx; VI v[N],C;
bool vis[N],cycle[N]; map <VI,int> rst;
inline bool find_cycle(CI now=1,CI lst=0)
{
for (int to:v[now]) if (to!=lst&&vis[to])
return s=to,t=now,1; else if (!vis[to])
{
vis[to]=1; pre[to]=now;
if (find_cycle(to,now)) return 1; vis[to]=0;
}
return 0;
}
inline void get_hash(CI now,CI fa=0)
{
VI tmp; for (int to:v[now]) if (to!=fa&&!cycle[to])
get_hash(to,now),tmp.push_back(hsh[to]);
sort(tmp.begin(),tmp.end());
if (!rst[tmp]) rst[tmp]=++idx; hsh[now]=rst[tmp];
}
int main()
{
for (scanf("%d",&T);T;--T)
{
RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) vis[i]=cycle[i]=0,v[i].clear();
for (i=1;i<=m;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
if (m==n-1) { puts("YES"); continue; }
if (m>n) { puts("NO"); continue; }
vis[1]=1; find_cycle(); cycle[s]=1; C.clear(); C.push_back(s);
for (idx=0,rst.clear();t!=s;t=pre[t]) cycle[t]=1,C.push_back(t);
for (int x:C) get_hash(x); bool flag=1;
for (i=1;i<C.size()&&flag;++i) if (hsh[C[i]]!=hsh[C[0]]) flag=0;
if (flag) { puts("YES"); continue; }
if (C.size()%2==1) { puts("NO"); continue; }
for (flag=1,i=2;i<C.size()&&flag;++i) if (hsh[C[i]]!=hsh[C[i&1]]) flag=0;
puts(flag?"YES":"NO");
}
return 0;
}
I. Guess Cycle Length
很有意思的一個互動,把隨機和 BSGS 兩種做法合起來才能透過
首先有個很自然的 BSGS 思想的做法,設一個閾值 \(S\),然後進行如下過程:
- 先進行 \(S\) 次
walk 1
操作; - 再進行 \(S\) 次
walk S
操作;
但這樣的話只能用 \(2S\) 次操作得到 \(S^2\) 級別的環,沒法直接透過
因此在此之前我們需要進行一些神秘的隨機操作,在本題中,直接先隨機 \(3000\) 次,每次步長在 \([1,10^9]\) 中隨機挑選,同時記錄下這個過程中遇到的編號最大的點 \(M\)
考慮 \(N-M\le 10^7\) 的機率,由於 \(10^7\) 是 \(10^9\) 的 \(\frac{1}{100}\),因此這個機率為 \(1-(\frac{99}{100})^{3000}\),可以認為不可能出錯
然後由於 \(3200^2\ge 10^7\),因此我們可以編出如下做法:
- 進行 \(3000\) 次隨機操作,記錄下遇到的點編號的最大值 \(M\);
- 進行 \(3200\) 次
walk 1
操作; - 進行 \(1\) 次
walk M
操作; - 進行 \(3200\) 次
walk 3200
操作;
注意到進行第三次操作後相當於回退了 \(N-M\) 步,因此我們只需要用那個 BSGS 的做法確定 \(N-M\) 的值即可
#include<cstdio>
#include<iostream>
#include<random>
#include<map>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
bool debug=0;
int n,cur,p[10000000];
map <int,int> mp;
mt19937 rng(0x0d000721);
inline int randint(CI l, CI r)
{
return uniform_int_distribution{l, r}(rng);
}
inline int ask(int x)
{
if (x==0) x=randint(1,1000000000);
if (debug)
{
(cur+=x)%=n; return p[cur];
} else
{
printf("walk %d\n",x); fflush(stdout);
int res; scanf("%d",&res); return res;
}
}
inline void answer(CI x)
{
printf("guess %d\n",x); fflush(stdout);
}
int main()
{
if (debug)
{
scanf("%d",&n); cur=0;
for (RI i=0;i<n;++i) p[i]=i;
shuffle(p,p+n,rng);
}
int M=0;
for (RI i=1;i<=3000;++i) M=max(M,ask(0));
for (RI i=1;i<=3200;++i)
{
int pos=ask(1);
if (mp.count(pos)) return answer(i-mp[pos]),0;
mp[pos]=i;
}
int pos=ask(M);
if (mp.count(pos)) return answer(M+3200-mp[pos]),0;
for (RI i=1;i<=3200;++i)
{
int pos=ask(3200);
if (mp.count(pos)) return answer(3200*i+M+3200-mp[pos]),0;
}
return 0;
}
J. Painting
徐神秒切的幾何,但好像細節有點多
大致思路就是非顯式地維護凸包,令擋住某條線段 \(p\) 的線段為 \(q\),則建一顆樹,\(q\) 就是 \(p\) 的父親
詢問時先求出在兩邊牆上距離當前線段最近的兩條線段 \(u,v\),則 \(u,v\) 在樹上的路徑構成了一個凸殼,在上面二分找交點即可
用倍增實現樹上操作,總複雜度 \(O(n\log n)\)
#include<bits/stdc++.h>
using namespace std;
#define int __int128
#define ft first
#define sd second
int gcd(int a, int b) {return 0==b ? a : gcd(b, a%b);}
struct Frac{
__int128 up, dn;
bool operator<(const Frac &b) {
return up*b.dn < dn*b.up;
}
pair<long long, long long> divd() {
int g = gcd(up, dn);
// printf("g=%d up=%d dn=%d\n", (signed)g, (signed)up, (signed)dn);
return {up/g, dn/g};
}
};
struct Pt {
int x, y;
Pt operator-(const Pt &b)const {return Pt{x-b.x, y-b.y};}
int crs(const Pt &b) const {return x*b.y - y*b.x;}
int check(pair<Frac, Frac> p, Pt ppp) {
int res = x*(p.sd.up - ppp.y*p.sd.dn) - y*(p.ft.up - ppp.x*p.ft.dn);
return (0==res ? 0 : (res>0 ? 1 : -1));
}
};
pair<Frac, Frac> ins(Pt p1, Pt v1, Pt p2, Pt v2) {
Frac x, y;
int res1 = v1.crs(v2), res2 = v2.crs(p1-p2);
x.dn = y.dn = res1;
x.up = p1.x*res1 + v1.x*res2;
y.up = p1.y*res1 + v1.y*res2;
if (res1<0) {
x.dn = -x.dn; y.dn = -y.dn;
x.up = -x.up; y.up = -y.up;
}
// printf("p1(%d %d) p2(%d %d) v1(%d %d) v2(%d %d)\n", (signed)p1.x, (signed)p1.y, (signed)p2.x, (signed)p2.y, (signed)v1.x, (signed)v1.y, (signed)v2.x, (signed)v2.y);
// printf("x.up=%d x.dn=%d res1=%d res2=%d\n", (signed)x.up, (signed)x.dn, (signed)res1, (signed)res2);
return {x, y};
}
const int INF = (int)1e6+5;
const int N = 3e5+5;
const int B = 21;
signed n, W;
int fa[B][N], dep[N];
Pt seg[N][2];
pair<Frac, Frac> insp[N];
set<pair<int, int>> st;
int LCA(int x, int y) {
if (dep[x]<dep[y]) swap(x, y);
for (int j=B-1; j>=0; --j) if (dep[fa[j][x]]>=dep[y]) x=fa[j][x];
if (y==x) return x;
for (int j=B-1; j>=0; --j) if (fa[j][x]!=fa[j][y]) x=fa[j][x], y=fa[j][y];
return fa[0][x];
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> W;
fa[0][0] = 0; dep[0] = 0;
fa[0][n+1] = fa[0][n+2] = 0; dep[n+1] = dep[n+2] = 1;
insp[n+1] = {{W,1}, {INF, 1}};
insp[n+2] = {{W,1}, {-INF, 1}};
st.insert({INF, n+1}); st.insert({-INF, n+2});
for (int i=1; i<=n; ++i) {
signed a, b, c; cin >> a >> b >> c;
seg[0][0] = {W, INF}; seg[0][1] = {W, -INF};
seg[i][0] = {0, a}; seg[i][1] = {W, b};
auto it2 = st.lower_bound({a, -1});
auto it1 = it2; --it2;
// printf("11111\n");
Pt v = Pt{W, b} - Pt{0, a};
int lca = LCA(it1->sd, it2->sd);
auto getFa = [&](int cur1, Pt ppp, int sg) {
if (v.check(insp[cur1], ppp)*sg < 0) return cur1;
for (int j=B-1; j>=0; --j) if (fa[j][cur1] && dep[fa[j][cur1]]>dep[lca]){
if (v.check(insp[fa[j][cur1]], ppp)*sg > 0) cur1 = fa[j][cur1];
}
cur1 = fa[0][cur1];
if (dep[cur1] <= dep[lca]) return (__int128)-1;
return cur1;
};
int cur;
int cur1 = getFa(it1->sd, seg[i][0], 1);
int cur2 = getFa(it2->sd, seg[i][0], -1);
// printf("i=%d cur1=%d\n", (signed)i, (signed)cur1);
// printf("i=%d cur2=%d\n", (signed)i, (signed)cur2);
if (-1==cur1 && -1==cur2) {
cur = lca;
insp[i] = ins(seg[i][0], seg[i][1]-seg[i][0], seg[lca][0], seg[lca][1]-seg[lca][0]);
} else {
Frac x1, y1, x2, y2;
if (-1==cur1) x1 = {INF, 1}, y1 = {INF, 1};
else {
auto pii = ins(seg[i][0], seg[i][1]-seg[i][0], seg[cur1][0], seg[cur1][1]-seg[cur1][0]);
x1 = pii.ft, y1 = pii.sd;
}
if (-1==cur2) x2 = {INF, 1}, y2 = {INF, 1};
else {
auto pii = ins(seg[i][0], seg[i][1]-seg[i][0], seg[cur2][0], seg[cur2][1]-seg[cur2][0]);
x2 = pii.ft, y2 = pii.sd;
}
if (x1 < x2) insp[i] = {x1, y1}, cur=cur1;
else insp[i] = {x2, y2}, cur=cur2;
}
auto [x1, y1] = insp[i];
auto [xup, xdn] = x1.divd();
auto [yup, ydn] = y1.divd();
cout << "(" << xup << "/" << xdn << "," << yup << "/" << ydn <<")\n";
// printf("i=%d cur=%d\n", (signed)i, (signed)cur);
if (c>0) {
st.insert({a, i});
fa[0][i] = cur;
dep[i] = dep[cur]+1;
for (int j=1; j<B; ++j) fa[j][i] = fa[j-1][fa[j-1][i]];
}
}
return 0;
}
K. Master of Both
徐神開場就秒了,我題意都沒看
#include <bits/stdc++.h>
constexpr int $n = 500005;
constexpr int $ss = 1000006;
int n, q;
int64_t ctr[26][26], pans = 0;
int go[$ss][26], sum[$ss], O = 0;
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n >> q;
for(int i = 0; i < n; ++i) {
std::string s; std::cin >> s;
int cur = 0;
for(auto ch: s) {
int u = ch - 'a';
for(int j = 0; j < 26; ++j) if(j != u && go[cur][j])
ctr[j][u] += sum[go[cur][j]];
if(!go[cur][u]) go[cur][u] = ++O;
cur = go[cur][u];
sum[cur]++;
}
for(int i = 0; i < 26; ++i) if(go[cur][i]) pans += sum[go[cur][i]];
}
// for(int i = 0; i < 26; ++i) for(int j = 0; j < 26; ++j)
// std::cerr << ctr[i][j] << char(j == 25 ? 10 : 32);
while(q--) {
std::string s; std::cin >> s;
int rk[26];
for(int i = 0; i < 26; ++i) rk[s[i] - 'a'] = i;
int64_t ans = pans;
for(int i = 0; i < 26; ++i) for(int j = 0; j < 26; ++j) if(rk[i] > rk[j] && ctr[i][j])
// std::cerr << "ctr[" << i << "][" << j << "] = " << ctr[i][j] << char(10),
ans += ctr[i][j];
std::cout << ans << char(10);
}
return 0;
}
M. Please Save Pigeland
注意到如果選擇在某個點 \(x\) 修醫院,則我們求出 \(x\) 到所有關鍵點的距離,記為 \(l_1,l_2,\dots,l_k\),則它的貢獻就是 \(2\times \frac{\sum_{i=1}^k l_i}{\gcd_{i=1}^k l_i}\)
考慮切換 \(x\) 時用換根 DP 維護這兩個值,分子顯然是很基礎的加減模型,但分母無法直接維護,因為涉及經過一條邊後的全體數加上一個值
但徐神神之一手發現可以令一個二元組 \((v,g)\) 來表示子樹資訊,\(v\) 表示一條到當前點的路徑的長度,\(g\) 表示剩下路徑與 \(v\) 的差值的 \(\gcd\)
這樣集體加 \(w\) 就有 \((v,g)\to (v+w,g)\);同時合併兩個子樹有 \((v_1,g_1)+(v_2,g_2)\to (v_1,\gcd(g_1,g_2,|v_1-v_2|))\),然後就也可以套換根 DP 模型了
實現的時候注意各種奇奇怪怪的細節,尤其是子樹內沒有關鍵點的狀態需要單獨設
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=500005,INF=1e18;
int n,k,x,y,z,key[N],ans,sum[N],sz[N]; vector <pi> v[N];
struct ifo
{
int v,g;
inline ifo(CI V=-1,CI G=-1)
{
v=V; g=G;
}
inline void add(CI x) { v+=x; }
friend inline ifo operator + (const ifo& A,const ifo& B)
{
if (A.v==-1) return B; if (B.v==-1) return A;
return ifo(A.v,__gcd(__gcd(A.g,B.g),abs(A.v-B.v)));
}
}f[N]; vector <ifo> pre[N],suf[N];
inline void DFS1(CI now=1,CI fa=0)
{
sz[now]=key[now]; sum[now]=0; if (key[now]) f[now]=ifo(0,0);
vector <pi> sons;
for (auto [to,w]:v[now]) if (to!=fa) sons.push_back({to,w});
v[now]=sons;
for (auto [to,w]:v[now])
{
DFS1(to,now); sz[now]+=sz[to];
sum[now]+=sum[to]+w*sz[to];
pre[now].push_back(f[to]+pre[to].back());
if (sz[to]) pre[now].back().add(w);
suf[now].push_back(f[to]+pre[to].back());
if (sz[to]) suf[now].back().add(w);
}
if (pre[now].empty()) pre[now].push_back(ifo());
if (suf[now].empty()) suf[now].push_back(ifo());
for (RI i=1;i<(int)pre[now].size();++i) pre[now][i]=pre[now][i-1]+pre[now][i];
for (RI i=(int)suf[now].size()-2;i>=0;--i) suf[now][i]=suf[now][i+1]+suf[now][i];
}
inline void DFS2(CI now=1,CI fa=0,CI out_sz=0,CI out_sum=0,const ifo& out_f=ifo())
{
int cur_sum=sum[now]+out_sum;
ifo cur_f=f[now]+pre[now].back()+out_f;
int cur_gcd=__gcd(cur_f.v,cur_f.g);
// printf("now = %lld, sum = %lld, gcd = %lld\n",now,cur_sum,cur_gcd);
if (cur_gcd!=0) ans=min(ans,cur_sum/cur_gcd); else ans=0;
for (RI i=0;i<v[now].size();++i)
{
auto [to,w]=v[now][i];
ifo tmp_f=f[now]+(i-1>=0?pre[now][i-1]:ifo())+(i+1<v[now].size()?suf[now][i+1]:ifo())+out_f;
if (out_sz+sz[now]-sz[to]) tmp_f.add(w);
DFS2(to,now,out_sz+sz[now]-sz[to],out_sum+sum[now]-(sum[to]+w*sz[to])+(out_sz+sz[now]-sz[to])*w,tmp_f);
}
}
signed main()
{
scanf("%lld%lld",&n,&k); ans=INF;
for (RI i=1;i<=k;++i) scanf("%d",&x),key[x]=1;
for (RI i=1;i<n;++i)
{
scanf("%lld%lld%lld",&x,&y,&z);
v[x].push_back({y,z}); v[y].push_back({x,z});
}
DFS1(); DFS2(); printf("%lld",2LL*ans);
return 0;
}
Postscript
接下來的幾天大家好像都有考試啥的,感覺不太抽得出時間來訓了
下週開始就要連戰三站了,希望能打個令人滿意的成績吧