assert(0);
不嘻嘻。
T1 棋局
首先不難列出 dp 方程 \(f[i][j]\) 表示玩了 \(i\) 局 A 贏了 \(j\) 局的方案數(我們這裡欽定玩了 \(R_m+R_h\) 局 A 贏了 \(R_m\) 局),轉移 \(f[i][j]\times \frac{j}{i}\to f[i+1][j+1],f[i][j]\times\frac{i-j}{i}\to f[i+1][j]\),仔細思考/畫圖/大眼觀察後發現係數是固定的,預處理逆元/階乘/階乘逆元之後直接乘就行了,還要乘一個欽定的組合數。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(register int i=l; i<=r; ++i)
#define dn(i,r,l) for(register int i=r; i>=l; --i)
using namespace std;
const int N=4000005, P=147744151;
int t, xx, yy, x, y, Ans, mul[N], inv[N], fac[N];
int C(int n,int m) {
if(m<0||n<m) return 0;
return mul[n]*fac[m]%P*fac[n-m]%P;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
mul[0]=inv[0]=inv[1]=fac[0]=1;
up(i,1,4000000) mul[i]=mul[i-1]*i%P;
up(i,2,4000000) inv[i]=inv[P%i]*(P-P/i)%P;
up(i,1,4000000) fac[i]=fac[i-1]*inv[i]%P;
cin >> t;
while(t--) {
cin >> xx >> yy >> x >> y;
Ans=C(xx+yy,xx), xx+=x, yy+=y;
Ans=Ans*fac[xx+yy-1]%P*mul[x+y-1]%P;
Ans=Ans*mul[xx-1]%P*fac[x-1]%P;
Ans=Ans*mul[yy-1]%P*fac[y-1]%P;
cout << Ans << '\n';
}
return 0;
}
T2 慶典
拆成兩個問題,到一個點的距離最長是多少,定向到這個點代價是多少,注意到每個點上都有遊客所以問題是一個分討的簡單問題,馬上做完了。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(register int i=l; i<=r; ++i)
#define dn(i,r,l) for(register int i=r; i>=l; --i)
#define pb push_back
using namespace std;
const int N=200005;
int n, d, f[N], g[N], Ans=1e18;
struct node { int v, w, opt; };
vector<node> to[N];
void dfs(int x,int fad) {
for(node p:to[x]) {
int y=p.v, w=p.w;
if(y==fad) continue;
dfs(y,x);
f[x]=max(f[x],f[y]+w);
g[x]+=g[y]+(!p.opt);
}
}
void solve(int x,int fad,int pre,int val) {
int sum=0;
vector<int> L, R;
L.pb(0), R.pb(0);
for(node p:to[x]) {
int y=p.v, w=p.w;
if(y==fad) continue;
L.pb(f[y]+w);
R.pb(f[y]+w);
}
L.pb(0), R.pb(0);
up(i,1,L.size()-1) L[i]=max(L[i-1],L[i]);
dn(i,R.size()-2,0) R[i]=max(R[i+1],R[i]);
int cnt=0;
for(node p:to[x]) {
int y=p.v, w=p.w;
if(y==fad) continue;
++cnt;
solve(y,x,max(pre,max(L[cnt-1],R[cnt+1]))+w,val+g[x]-g[y]-(!p.opt)+p.opt);
}
if(max(f[x],pre)<=d) Ans=min(Ans,n-1-g[x]-val);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> d;
up(i,2,n) {
int u, v, w;
cin >> u >> v >> w;
to[u].pb((node){v,w,1});
to[v].pb((node){u,w,0});
}
dfs(1,0), solve(1,0,0,0);
if(Ans==1e18) Ans=-1;
cout << Ans;
return 0;
}
T3 遊戲
考慮到固定一個左端點之後往右擴充只會有 \(\log V\) 種本質不同的 \(\gcd\),我們先列舉被刪除的最左邊的數 \(i\) 那會有一個 \(\gcd(a_l,\dots,a_{i-1})\) 的貢獻,這種貢獻本質不同的段數顯然也是 \(\log\) 的,我們考慮把這一段一起考慮,發現取最右邊的最好,因為左邊貢獻一致的情況下可以儘可能讓後面的 \(\gcd\) 更大。按照這個思路進行列舉列舉量最多是 \(\log^3 V\) 的,寫 st 表+二分 gcd 可以做到 3 只,但因為常數小 \(O(n\log^4V)\) 可以透過,太牛了。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(register int i=l; i<=r; ++i)
#define dn(i,r,l) for(register int i=r; i>=l; --i)
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
using namespace std;
const int N=400005, M=log2(N)+1;
int n, q, t, a[N], f[N][M], lg[N];
vector<pii> L[N], lyl, lsy;
inline int gcd(int a,int b) {
if(a==0||b==0) return a|b;
int az=__builtin_ctz(a), bz=__builtin_ctz(b), z=min(az,bz), dif;
b>>=bz;
while(a) {
a>>=az, dif=b-a;
az=__builtin_ctz(dif);
if(a<b) b=a;
a=dif<0?-dif:dif;
}
return b<<z;
}
inline int qur(int l,int r) {
if(l>r) return 0;
int k=lg[r-l+1];
return gcd(f[l][k],f[r-(1<<k)+1][k]);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> q, t=log2(n);
up(i,1,n) cin >> a[i], f[i][0]=a[i], lg[i]=log2(i);
up(j,1,t) up(i,1,n) f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
dn(i,n,1) {
int lst=0;
for(pii p:lyl) {
int x=p.first, d=gcd(p.second,a[i]);
if(d!=lst) lst=d, lsy.pb(mp(x,d));
}
if(i==n||a[i+1]%a[i]) lsy.pb(mp(i,a[i]));
lyl=lsy, lsy.clear();
for(pii p:lyl) L[i].pb(mp(p.first+1,p.second));
L[i].pb(mp(i,0)), reverse(L[i].begin(),L[i].end());
}
while(q--) {
int l, r, k;
cin >> l >> r >> k;
k=(r-l+1)-k;
if(k==0) {
cout << qur(l,r) << '\n';
}
if(k==1) {
int Ans=qur(l,r-1);
for(pii p:L[l]) {
int i=p.first;
if(i>=r) break;
Ans=max(Ans,gcd(p.second,qur(i+1,r)));
}
cout << Ans << '\n';
}
if(k==2) {
int Ans=qur(l,r-1);
for(pii p:L[l]) {
int i=p.first;
if(i>=r) break;
Ans=max(Ans,gcd(p.second,qur(i+1,r-1)));
for(pii q:L[i+1]) {
int j=q.first;
if(j>=r) break;
Ans=max(Ans,gcd(gcd(p.second,q.second),qur(j+1,r)));
}
}
cout << Ans << '\n';
}
if(k==3) {
int Ans=qur(l,r-1);
for(pii p:L[l]) {
int i=p.first;
if(i>=r) break;
Ans=max(Ans,gcd(p.second,qur(i+1,r-1)));
for(pii q:L[i+1]) {
int j=q.first;
if(j>=r) break;
Ans=max(Ans,gcd(gcd(p.second,q.second),qur(j+1,r-1)));
for(pii t:L[j+1]) {
int k=t.first;
if(k>=r) break;
Ans=max(Ans,gcd(gcd(p.second,q.second),gcd(t.second,qur(k+1,r))));
}
}
}
cout << Ans << '\n';
}
}
return 0;
}
T4 吃豆人
首先先二分一個速度 \(v\) 轉化成判定性問題,位置二元組難以維護但是注意到 \(pos_{i-1}\) 一定在裡面,所以我們維護合法的另一點即可,容易分討做到 \(O(n\log n)\) 的判定,但是會超時,注意到一個時刻合法的另一個端點是區間(可以觀察後透過分討做法推得),純度分討 \(O(n)\) 判定即可。
#include<bits/stdc++.h>
#define int long long
#define db long double
#define up(i,l,r) for(register int i=l; i<=r; ++i)
#define dn(i,r,l) for(register int i=r; i>=l; --i)
using namespace std;
const int N=100005;
const db eps=1e-10;
int n; db t[N], pos[N];
bool check(db v) {
db l=0, r=0;
up(i,0,n-1) {
db len=(t[i+1]-t[i])*v;
bool L=l-len<=pos[i+1]&&pos[i+1]<=r+len;
bool R=pos[i]-len<=pos[i+1]&&pos[i+1]<=pos[i]+len;
if(L&&R) l=min(l-len,pos[i]-len), r=max(r+len,pos[i]+len);
else if(R) l-=len, r+=len;
else if(L) l=pos[i]-len, r=pos[i]+len;
else return 0;
}
return 1;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
up(i,1,n) cin >> t[i] >> pos[i];
db l=0, r=1e7;
while(l+eps<r) {
db mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
cout << fixed << setprecision(10) << l;
return 0;
}