gjoi 2024.9.27

Hypoxia571發表於2024-09-28

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;
}

相關文章