GJ Round

lunjiahao發表於2024-10-20

前言:

GJ Round 為學校內部模擬賽

記 7.13 為 Round 1,在此之前的模擬賽較為混亂以後再說(可能記為 Round 0 或者負數)

目前正在倒序更新

https://www.luogu.com.cn/article/a30ffmdb

Round 20 (9.18)

\(\mathcal A\)

簡單數學題

考慮將每一個 \(a_i\) 分別拆開計算貢獻計算對應的 \(a_i=i\) 時所產生的貢獻即可

答案即為 \(ans=\sum_{i=1}^{n} ({i-1 \choose a_i-1} \times 2^{n-i}) \pmod {10^9+7}\)

#include<bits/stdc++.h>
#define int long long
#define lowbit(x) (x&-x)
using namespace std;
const int N=2e5+5,mod=1e9+7;
int n,a[N],fac[N],inv[N],pw2[N];
int fpm(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int C(int n,int k)
{
	return (k>n||n<0||k<0)?0:fac[n]*inv[n-k]%mod*inv[k]%mod;
}
void init()
{
	fac[0]=pw2[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod,pw2[i]=pw2[i-1]*2%mod;
	inv[n]=fpm(fac[n],mod-2);
	for(int i=n;i;i--) inv[i-1]=inv[i]*i%mod;
}
signed main()
{
//	freopen("ex_seq.in","r",stdin);
//	freopen("ex_seq.out","w",stdout);
	scanf("%lld",&n);
	init();
	for(int i=1;i<=n;i++) scanf("%lld",a+i);
	sort(a+1,a+1+n);
	int ans=0;
	for(int i=1;i<=n;i++) ans=(ans+C(i-1,a[i]-1)*pw2[n-i]%mod)%mod;
	printf("%lld",ans);
	return 0;
}

\(\mathcal B\)

按順序從 \(1\)\(n\) 拿盤子,對於每個盤子,你可以選擇不要,也可以拿走,但拿走盤子需要滿足下面三個條件至少一個:

  • 之前沒有拿過盤子;

  • 這個盤子的尺寸比之前拿走的盤子的都大,這樣你就可以把之前買的盤子疊在它的上面;

  • 這個盤子的尺寸比之前拿走的盤子的都小,這樣你就可以把它疊在之前買的盤子的上面。

最後,你想知道,你最多能拿走多少個盤子?

從結尾開始做最長上升和最長下降子序列,二分或線段樹最佳化 dp 即可

#include<bits/stdc++.h>
#define ls u<<1
#define rs u<<1|1
using namespace std;
const int N=1e5+5;
int n,f[N],g[N],a[N],b[N];
struct SGT
{
	int t[N<<2];
	void push_up(int u)
	{
		t[u]=max(t[ls],t[rs]);
	}
	void update(int u,int l,int r,int x,int k)
	{
		if(x<l||r<x) return;
		if(x==l&&r==x)
		{
			t[u]=k;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid) update(ls,l,mid,x,k);
			else update(rs,mid+1,r,x,k);
		push_up(u);
	}
	int query(int u,int l,int r,int x,int y)
	{
		if(y<l||r<x) return 0;
		if(x<=l&&r<=y) return t[u];
		int mid=(l+r)>>1,res=0;
		if(x<=mid) res=max(res,query(ls,l,mid,x,y));
		if(y>mid) res=max(res,query(rs,mid+1,r,x,y));
		return res;
	}	
}T1,T2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i),b[i]=a[i];
	sort(b+1,b+1+n);
	int len=unique(b+1,b+1+n)-b-1;
	for(int i=n;i;i--)
	{
		a[i]=lower_bound(b+1,b+1+len,a[i])-b;
		int x=(1<=a[i]-1?T1.query(1,1,n,1,a[i]-1):0);
		int y=(a[i]+1<=n?T2.query(1,1,n,a[i]+1,n):0);
		f[i]=x+1,g[i]=y+1;
		T1.update(1,1,n,a[i],f[i]);
		T2.update(1,1,n,a[i],g[i]);
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,f[i]+g[i]-1);
	printf("%d",ans);
	return 0;
}

時間複雜度 \(\mathcal O(n \log n)\)

\(\mathcal C\)

CF875F Royal Questions

考慮將平面內的每一行每一列連邊

求最大權值基環森林

跟最大生成樹差不多,用 Kruskal 跑再稍微改一下就差不多了

時間複雜度 \(O(nm (\log n+\log m))\)

\(\mathcal D\)

天道酬勤,你已經精通了 OI。

你認為 OI 的學習可以分為 \(n\) 個階段,在經歷第 \(i\) 個階段時,如果自身能力值大於 \(a_i\),那麼就可以得到 \(b_i\) 的進步,也就是能力值累加上 \(b_i\)

並不是每個 Oler 都會完整的走完 \(n\) 個階段,你觀察了 \(q\) 個 Oler,第 \(i\) 個 Oler 會帶著 \(x_i\) 的初始能力值依次經歷第 \(l_i,l_{i+1},\dots,r_i\) 個階段。

他們都還在路上,而你想知道他們最終會變得多強,也就是經歷完這些階段後的能力值。

由於某些原因,有時候你急切的想知道答案。

資料結構(DS)題

咕咕咕

Round 21 (9.19)

\(\mathcal A\)

SB 題,還什麼困難卷積

不同的 \(a_i,b_i\) 只有 \(\mathcal O(\sqrt{sum})\) 個,排序去重模擬即可

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+5;
int n,a[N],b[N],f[N],g[N];
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",a+i),f[a[i]]++;
	for(int i=1;i<=n;i++) scanf("%lld",b+i),g[b[i]]++;
	sort(a+1,a+1+n),sort(b+1,b+1+n);
	int A=unique(a+1,a+1+n)-a-1,B=unique(b+1,b+1+n)-b-1;
	int ans=0;
	for(int i=1;i<=A;i++)
		for(int j=1;j<=B;j++)
			ans+=(int)sqrt(abs(a[i]-b[j]))*f[a[i]]*g[b[j]];
	printf("%lld",ans);
	return 0;
}
/*
4
1 2 3 4
2 3 3 3

12
*/

\(\mathcal B\)

CF623C Electric Charges

二分答案,預處理前字尾最大最小值,分別對於 \(x\)\(y\) 得到極長段來求答案

\(\mathcal C\)

數論題,不會做

\[ans=\sum_{i=0}^{p-1} a_i \times m^i \pmod p \]

\[p \in {Prime},m \in [1,p),a_0=1,a_1=1,a_2=6 ,\dots \]

\(a_i\) 是楊輝三角中間對稱軸的那一列

咕咕咕

\(\mathcal D\)

紅茶國有 \(m\) 個部落,為了爭奪 \(n\) 個有靈氣的礦洞裡的資源,部落之間經常發生衝突。礦洞被標號為 \(1\)\(n\),每個礦洞初始都被至多一個部落所佔領。平時的礦洞裡沒有任何有價值的資源,珍貴之物只有在特定的時候才會出現,具體地,依次會有 \(q\) 次事件發生,每次事件形如:

  • 1 l r x\(x\) 號部落發起戰爭,佔領了編號為 \(l\)\(r\) 的礦洞,原先佔有這些礦洞的部落將會失去它們,轉而由 \(x\) 號部落來佔領;

  • 2 l r x:編號為 \(l\)\(r\) 的礦洞靈氣爆發,都出現了價值為 \(x\) 的寶物。

一旦一個部落佔領的礦洞裡有寶物,寶物會立即被全部取走。為了知道哪些部落能成為王,你需要求出 \(q\) 次事件發生之後,每個部落分別得到的寶物的價值總和。

資料結構(DS)題

賽時著真做結果因為複雜度寫假了T飛了

聽說正著寫能用 set 維護顏色段,但筆者不會

由於沒有強制線上,可以考慮時光倒流,這樣就不用考慮正著做部落具有佔領權這一問題,那就基本上是區間加、區間覆蓋、區間查詢的模板線段樹題

最後再把一開始部落佔有的礦洞再 query \(m\) 次就好了

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
#define ls u<<1
#define rs u<<1|1
using namespace std;
const int N=5e5+5;
int a[N],ans[N];
int n,m,Q;
int t[N<<2],tag[N<<2],cov[N<<2];
void push_up(int u)
{
	t[u]=t[ls]+t[rs];
}
void build(int u,int l,int r)
{
	cov[u]=-1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(u);
}
void push_down(int u,int l,int r)
{
	if(~cov[u])
	{
		cov[ls]=cov[rs]=cov[u];
		tag[ls]=tag[rs]=0;
		t[ls]=t[rs]=cov[u];
		cov[u]=-1;
	}
	if(!tag[u]) return;
	tag[ls]+=tag[u],tag[rs]+=tag[u];
	int mid=(l+r)>>1;
	t[ls]+=tag[u]*(mid-l+1);
	t[rs]+=tag[u]*(r-mid);
	tag[u]=0;
}
void update(int u,int l,int r,int x,int y,int k)
{
	if(y<l||r<x) return;
	if(x<=l&&r<=y)
	{
		t[u]+=(r-l+1)*k;
		tag[u]+=k;
		return;
	}
	push_down(u,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) update(ls,l,mid,x,y,k);
	if(y>mid) update(rs,mid+1,r,x,y,k);
	push_up(u);
}
void modify(int u,int l,int r,int x,int y,int k)
{
	if(y<l||r<x) return;
	if(x<=l&&r<=y)
	{
		t[u]=cov[u]=k;
		tag[u]=0;
		return;
	}
	push_down(u,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) modify(ls,l,mid,x,y,k);
	if(y>mid) modify(rs,mid+1,r,x,y,k);
	push_up(u);
}
int query(int u,int l,int r,int x,int y)
{
	if(y<l||r<x) return 0;
	if(x<=l&&r<=y) return t[u];
	push_down(u,l,r);
	int mid=(l+r)>>1,res=0;
	if(x<=mid) res+=query(ls,l,mid,x,y);
	if(y>mid) res+=query(rs,mid+1,r,x,y);
	return res;
}
struct dat
{
	int opt,x,y,k;
}p[N];
signed main()
{
//	freopen("king.in","r",stdin);
//	freopen("king.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&Q);
	for(int i=1;i<=n;i++) scanf("%lld",a+i);
	build(1,1,n);
	for(int i=1;i<=Q;i++) scanf("%lld%lld%lld%lld",&p[i].opt,&p[i].x,&p[i].y,&p[i].k);
	for(int i=Q;i;i--)
	{
		int opt=p[i].opt,x=p[i].x,y=p[i].y,k=p[i].k;
		if(opt&1)
		{
			ans[k]+=query(1,1,n,x,y);
			modify(1,1,n,x,y,0);
			continue;
		}
		update(1,1,n,x,y,k);
	}
	for(int i=1;i<=n;i++) ans[a[i]]+=query(1,1,n,i,i);
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

Round 22 (9.24)

\(\mathcal A\)

小模擬麻將題

  • 先考慮 \(n=14\)

    \(k=2\) 只考慮順子和雀頭

    \(k=3,4\) 列舉雀頭再檢驗剩下的牌是刻子還是順子

  • 在考慮 \(n=13\)

    顯然可以列舉最後一張牌,然後就用 \(n=14\) 時的方法做就好

\(\mathcal B\)

人類智慧題

發現 \(0 \leq y_i \leq 10\),可以先按 \(x_i\) 從小到大排序,然後發動人類智慧,每個點只連它前面的 \(20\) 個點,然後跑最小生成樹,做完了

時間複雜度 \(\mathcal O(n \log n)\)

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=2e6+5;
int n,cnt,fa[N],siz[N];
struct dat
{
	int x,y;
}p[N];
bool cmp(const dat &a,const dat &b)
{
	return a.x^b.x?a.x<b.x:a.y<b.y;
}
int dis(int a,int b,int x,int y)
{
	return (a-x)*(a-x)+(b-y)*(b-y);
}
struct Edge
{
	int u,v,w;
}e[M];
bool cmpe(const Edge &a,const Edge &b)
{
	return a.w<b.w;
}
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int Kruskal()
{
	int tot=0,mst=0;
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	sort(e+1,e+1+cnt,cmpe);
	for(int i=1;i<=cnt;i++)
	{
		int x=find(e[i].u),y=find(e[i].v);
		if(x==y) continue;
		if(siz[x]<siz[y]) swap(x,y);
		fa[y]=x;
		siz[x]+=siz[y];
		mst+=e[i].w;
		if(++tot==n-1) break;
	}
	return mst;
}
signed main()
{
//	freopen("ant.in","r",stdin);
//	freopen("ant.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].x,&p[i].y);
	sort(p+1,p+1+n,cmp);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=min(i+20,n);j++)
			e[++cnt]=(Edge){i,j,dis(p[i].x,p[i].y,p[j].x,p[j].y)};
	printf("%lld",Kruskal());
	return 0;
}

\(\mathcal C\)

咕咕咕

\(\mathcal D\)

咕咕咕

Round 23 (9.27)

\(\mathcal A\)

求機率題,也就模數換成了 \(147744151\) (還好是質數)

\(\mathcal B\)

類似於是換根 dp

先跑兩次 dfs 求一下樹的直徑,先以 \(1\) 為根跑一次統計轉向邊的數量,再跑一次 dfs 換根就貢獻即可

時間複雜度 \(\mathcal O(n)\),不知道題解在幹什麼搞倍增帶一隻 \(\log\)

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N=2e5+5,INF=0x3f3f3f3f;
int n,D,dis1[N],dis2[N],cst[N],ans,sum;
struct Edge
{
	int v,w,c;
};
vector <Edge> g[N];
void dfs1(int u,int fa)
{
	for(auto [v,w,c]:g[u])
	{
		if(v==fa) continue;
		dis1[v]=dis1[u]+w;
		dfs1(v,u);
	}
}
void dfs2(int u,int fa)
{
	for(auto [v,w,c]:g[u])
	{
		if(v==fa) continue;
		sum+=c;
		dfs2(v,u);
	}
}
void dfs3(int u,int fa,int s)
{
	if(dis1[u]<=D&&dis2[u]<=D) ans=min(ans,s);
	for(auto [v,w,c]:g[u])
	{
		if(v==fa) continue;
		dfs3(v,u,s+(c==1?-1:1));
	}
}
int main()
{
//	freopen("ex_b1.in","r",stdin);
//	freopen("ex_b1.out","w",stdout);
	scanf("%d%d",&n,&D);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		g[u].eb((Edge){v,w,1});
		g[v].eb((Edge){u,w,0});
	}
	int x=0;
	dfs1(1,0);
	for(int i=1;i<=n;i++)
		if(dis1[i]>dis1[x]) x=i;

	dis1[x]=0;
	dfs1(x,0);
	memcpy(dis2,dis1,sizeof(dis1));
	for(int i=1;i<=n;i++)
		if(dis1[i]>dis1[x]) x=i;

	dis1[x]=0;
	dfs1(x,0);
	
	dfs2(1,0);
	
	ans=INF;
	dfs3(1,0,sum);
	printf("%d",ans==INF?-1:ans);
	return 0;
}
/*
6 8
2 1 1
2 3 3
2 4 6
1 5 5
6 1 3
*/

\(\mathcal C\)

神秘題,在序列 \(a\) 的區間 \([l,r]\) 中選 \(k\) 個數,最大化 \(\gcd(b_1,b_2,\dots,b_k)\)

\(k^\prime \gets (r-l+1)-k\),然後不會

\(\mathcal O(n \log^4 V)\) 真能過嗎

咕咕咕

\(\mathcal D\)

咕咕咕

Round 24 (9.29)

\(\mathcal A\)

結論題,不過一開始沒能立刻看出條件其實推下去就是對於 \(\forall i \in [1,n]\) 都滿足

就跟奇偶性有關

\(\mathcal B\)

有點意思,需要選出來的數中 \(\forall i,j \in [L,R],a_i \mid a_j \lor a_j \mid a_i\)

較為簡單的數論題,從最大值 \(maxa\)\(1\) 一直列舉,然後進 dfs 剪枝求答案即可

\(\mathcal C\)

咕咕咕

\(\mathcal D\)

咕咕咕

\(\mathcal E\)

咕咕咕

Round 25 (10.5)

\(\mathcal A\)

先按 \(l_i\) 從小到大排序,再按 \(r_i\) 從小到大排序

考慮貪心,維護兩個小根堆,一個是已匹配的,一個是未匹配的

能匹配的就匹配(廢話),不能匹配的從已匹配的中找一個小一點 \(r_j\) 來匹配就好

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define VI vector<int>::iterator
#define PII pair<int,int>
#define mp make_pair
using namespace std;
const int N=4e5+5,INF=0x3f3f3f3f;
int n,ans;
struct dat
{
	int l,r;
}a[N];
bool cmp(dat a,dat b)
{
	return a.l^b.l?a.l<b.l:a.r<b.r;
}
priority_queue<PII,vector<PII>,greater<PII>> X,Y;
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&a[i].l,&a[i].r);
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		if(!Y.empty()&&Y.top().first<a[i].l)
		{
			Y.pop();
			X.push(mp(a[i].r,a[i].l));
		}
		else if(!X.empty()&&X.top().first<a[i].r)
		{
			Y.push(X.top()),X.pop();
			X.push(mp(a[i].r,a[i].l));
		}
		else Y.push(mp(a[i].r,a[i].l));
	}
	printf("%d",X.size());
	return 0;
}

\(\mathcal B\)

咕咕咕

\(\mathcal C\)

咕咕咕

\(\mathcal D\)

咕咕咕

Round 26 (10.6)

\(\mathcal A\)

簡單數學題,求方程有整數解 \(x^2-2Bx+C=0,(B,C)\) 的對數

#pragma GCC optimize (3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int T,a[N];
signed main()
{
//	freopen("equation.in","r",stdin);
//	freopen("equation.out","w",stdout);
	for(int i=0;i<=1e6;i++)
		for(int j=i;j>=0;j--)
		{
			if(i*i-j*j>1e6) break;
			a[i*i-j*j]++;
		}
	for(int i=1;i<=1e6;i++) a[i]+=a[i-1];
	scanf("%lld",&T);
	while(T--)
	{
		int L,R;
		scanf("%lld%lld",&L,&R);
		printf("%lld\n",a[R]-a[L-1]);
	}
	return 0;
}
/*
sqrt(B^2-C) = Z
B^2-C=x^2
*/

\(\mathcal B\)

考慮歸納,因為 \(a_i=i\) 會變成不動點,所以在交換的過程中可能需要錯排,而錯排是有解的

\(\mathcal C\)

洛谷 P6864 [RC-03] 記憶

很好的一道資料結構(DS)題

這題最重要是考慮到如何拆分序列以便於統計與更新答案

發現答案會與某些東西在每個操作都存在一定關係,那麼可以試著上矩陣來維護,每次用線段樹單點修改、區間查詢即可

\(\mathcal D\)

真看不懂給的題解,咕咕咕

Round 27 (10.9)

\(\mathcal A\)

簡單數論分塊,過

\(\mathcal B\)

確實有點難推出結論

\(ax+by=c\) 非負整數對 \((x,y)\) 的個數,其中 \(a \in [0,N],y \in [0,M],x=Fib_{2k+1},y=_{2k+2},k \in \mathbb{N}\)

擴充套件歐幾里得演算法(exgcd)即可

用歸納法證明出 \(-Fib_iFib_{i-1}+Fib_{i+1}Fib_{i-2}=1\) 省去 exgcd\(\log\)笑死,筆者覺得不如觀察輸出對應的 \(\sout{(x,y)}\) 得出結論簡單

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=105;
int T,n,Fib[maxn],X,N,M; 
int solve(int A,int B,__int128 x,__int128 y)
{
	x*=X,y*=X; 
	x-=(-y+A-1)/A*B,y+=(-y+A-1)/A*A;
	if(x<0||y<0) return 0;
	if(x>N) y+=(x-N+B-1)/B*A,x-=(x-N+B-1)/B*B;
	if(y>M) x+=(y-M+A-1)/A*B,y-=(y-M+A-1)/A*A;
	if(x<0||y<0) return 0;
	if(x>N||y>M) return 0;
	return min(x/B,(M-y)/A)+min((N-x)/B,y/A)+1;
} 
signed main()
{
	int T;
	scanf("%lld",&T);
	Fib[1]=Fib[2]=1;
	for(int i=3;i<=100;i++) Fib[i]=Fib[i-1]+Fib[i-2];
	while(T--)
	{
		scanf("%lld%lld%lld",&X,&N,&M);
		if(!X)
		{
			puts("1");
			continue;
		}
		int ans=0;
		for(int i=1;i<=43;i++) ans+=solve(Fib[i*2-1],Fib[i*2],Fib[i*2-1],-Fib[i*2-2]); 
		printf("%lld\n",ans);
	}
    return 0;
}
/*
6
10 6 9
11 9 2
17 7 5
183 54 20
1919 810 114514
1121135 421443 428543
*/

\(\mathcal C\)

AT_joisc2014_e 水筒

先跑一次 bfs 建圖,然後跑 Kruskal 最小生成樹得到重構樹,最後在樹上跳倍增 LCA 求答案即可

(思路與 洛谷 P1967 [NOIP2013 提高組] 貨車運輸 相似,就多了個 bfs 建圖個過程)

\(\mathcal D\)

洛谷 P7363 [eJOI2020 Day2] Dots and Boxes

咕咕咕

Round 28 (10.10)

譴責 GJ 今天一開始只放一道題,名字叫做 \(\sout 5\) 道題

\(\mathcal A\)

簡單結論題,\(a_1<a_n\) 就符合了,過

\(\mathcal B\)

普通樹論題,但是不知道這個結論:劃分成大小為 \(k\) 的連通塊,當且僅當樹上有 \(n/k\) 個點的 \(size\)\(k\) 的倍數

\(\mathcal C\)

思路有點巧妙,暴力是 dp,發現每個物品的價值都很小,考慮大小約為 \(100 \times 100\) 矩陣快速冪加快遞推

\(\mathcal D\)

不會,咕咕咕

\(\mathcal E\)

數論題,更加不會,看到答案是對 \(2^{32}\) 取模就知道不簡單

掛個柿子以後再來補:

\[\begin{aligned} G(n) &= \prod_{i=1}^{n} (2i-1),G(0)=0 \\ ans &= \sum_{i=0}^{n} \sum_{j=0}^{m} G(i \operatorname{xor} j \operatorname{xor} x) \pmod {2^{32}} \end{aligned} \]

似乎要拆分貢獻?

咕咕咕

Round 29 (10.11)

\(\mathcal A\)

傻波一小明就會做虧本買賣是吧

題目在下面寫了個 \(u<v\),才發現是有向無向圖 \(DAG\),雖然不一定聯通

考慮拓撲排序 + dp,時間複雜度 \(\mathcal O(n+m)\)

\(\mathcal B\)

找規律題,與楊輝三角掛鉤

求的就是每一行前 \(a_i+1\) 個數的和,即第 \(a_i\) 列的值

答案為 $ \sum_{i=1}^{n+1} {i+a_i-1 \choose i} $

\(\mathcal C\)

AT_joisc2017_c 手持ち花火 (Sparklers)

所有人都向 \(k\) 號跑最優,考慮時光倒流,二分,check 裡貪心,後面不會,咕咕咕

\(\mathcal D\)

有一棵 \(n\) 個點的樹,帶有邊權。現有 \(m\) 個詢問如下:在樹上選取 \(k_i\) 條路徑(樹上任意兩點的連線通路視為一條路徑),其中至少要有一條路徑覆蓋到點 \(a_i\),問所有被路徑覆蓋的邊權的和最大是多少。注意重複覆蓋的邊只會計算一次。

樹上問題,不容易發現答案包含直徑某一端點,長鏈剖分,後面不會,咕咕咕

Round 30 (10.14)

\(\mathcal A\)

你作為一名尋寶者,來到了一個古老而神奇的城堡。城堡由多個房間組成,房間之間由牆壁隔開,從一個房間到另一個房間唯一的方法就是任意門傳送

城堡的地圖可以由一個 \(n\)\(m\) 列的網格圖表示,每個格子可能是空間區域(用 \(1\) 表示)或牆壁區域(用 \(0\) 表示)。任意兩個共享一邊的空間區域被認為屬於同一個房間。

你可以由一個空間區域 \((x_1,y_1)\) 前往另一個空間區域 \((x_2,y_2)\)

  • 操作 \(1\) - 步行:如果 \((x_1,y_1)\)\((x_2,y_2)\) 屬於同一個房間,那麼你可以花費 \(0\) 體力直接從 \((x_1,y_1)\) 走到 \((x_2,y_2)\)

  • 操作 \(2\) - 任意門傳送:如果 \((x_1,y_1)\)\((x_2,y_2)\) 不屬於同一個房間,那麼你可以花費 \(|x_1-x_2|+|y_1-y_2|\) 的體力從 \((x_1,y_1)\) 傳送至 \((x_2,y_2)\)

注意,如果 \((x_1,y_1)\)\((x_2,y_2)\) 屬於同一個房間,你只能選擇步行前往,不能透過傳送前往。

現在,你計劃從位置 \((x_s,y_s)\) 前往位置 \((x_t,y_t)\),你可以進行任意多次步行和任意門傳送。你可以重複經過同一個房間、也可以重複經過同一個空間區域。

為了更好的體驗任意門的奇妙感覺,你希望使用傳送的次數儘可能多。同時,你所消耗的體力值不能超過直接傳送體力值:定義直接傳送體力值至多經過一次傳送到達 \((x_t,y_t)\) 的最小體力值消耗。

你想知道,從 \((x_s,y_s)\) 前往任意一個空間區域,在所消耗的體力值不超過直接傳送體力值的前提下,最多能夠使用多少次傳送?

難繃,上來就搞神秘 bfs 題,不會,咕咕咕

\(\mathcal B\)

CF1310E Strange Function

模擬賽上 CF *2900 dp 也是隻有 GJ 敢這麼幹的

(題外話:賽時 puts("1");\(28\) pts 真不錯「伏筆」)

考慮分類討論

  • \(k=1\) 時,將 \(n\) 個元素劃分,上個揹包就好

  • \(k=2\) 時,對最終序列從大到小排序,差分再上揹包就好

  • \(k>2\) 時,因為答案已經不多了,所以直接搜尋剪枝就過了(這就是為啥能總司令了~)

\(\mathcal C\)

構造題

構造每一行形如 $\underline{ryxy} \dots \underline{ryxy} $、大小為 \(40 \times 40\) 的矩陣就好了再把最後一列也這樣搞,剛好是 \(2223\) 個,比法定最大 \(n\) 還多 \(1\)

然後就交給隨機化每次隨機更改一個位置求貢獻就好了

時間複雜度:\(\mathcal O(Tm^2)\),其中 \(T=rand(),m=40\),理論上 \(T ∝ \frac{1}{n}\)

#include<bits/stdc++.h>
#define R 1
#define Y 2
#define X 3
using namespace std;
const int N=45;
const int dx[8]={-1,1,0,0,-1,1,-1,1};
const int dy[8]={0,0,-1,1,-1,1,1,-1};
int n,a[N][N],b[N][N];
int query()
{
	int res=0;
	for(int i=1;i<=40;i++)
		for(int j=1;j<=40;j++)
			for(int k=0;k<=6;k+=2)
			{
				if(a[i][j]!=Y) break;
				int A=a[i+dx[k]][j+dy[k]];
				int B=a[i+dx[k+1]][j+dy[k+1]];
				if(A==R&&B==X) res++;
				if(A==X&&B==R) res++;
			}
	return res;
}
mt19937 rd(time(NULL));
int rnd(int l,int r)
{
	return rd()%(r-l+1)+l;
}
void init()
{
	for(int i=1;i<=40;i++)
		for(int j=1;j<=37;j+=4)
			a[i][j]=R,a[i][j+1]=Y,a[i][j+2]=X,a[i][j+3]=Y;
	for(int i=1;i<=37;i+=4)
		a[i][40]=R,a[i+1][40]=Y,a[i+2][40]=X,a[i+3][40]=Y;
}
int main()
{
	scanf("%d",&n);
	init();
	memcpy(b,a,sizeof(a));
	printf("40 40\n");
	while(query()>n)
	{
		int x=rnd(1,40),y=rnd(1,40);
		a[x][y]=X;
		if(query()<n) a[x][y]=b[x][y];
	}
	for(int i=1;i<=40;i++)
	{
		for(int j=1;j<=40;j++)	
			printf("%c",a[i][j]==R?'r':a[i][j]==Y?'y':'x');
		printf("\n");
	}
	return 0;
}

\(\mathcal D\)

對排列 \(\lbrace s_n \rbrace\),定義 \(g(k,i)=\min(s_i,s_{i+1}, \dots ,s_{i+k-1}),G(k)=\max_{i=1}^{n-k+1} g(k,i)\)

現給出 \(G(1),G(2), \dots ,G(n)\),求有多少個滿足要求的排列。

注:排列 \(\lbrace s_n \rbrace\)\(1\)\(n\)\(n\) 個整數按照任意順序排成一列後得到的序列,\(s_i\) 表示排在第個位置的數字。例如當 \(n=3\) 時表示長度為 \(3\) 的排列,共有 \(6\) 種可能,分別是:

\(\lbrace1,2,3\rbrace,\lbrace1,3,2\rbrace,\lbrace2,1,3\rbrace,\lbrace2,3,1\rbrace,\lbrace3,1,2\rbrace,\lbrace3,2,1\rbrace\)

咕咕咕

Round 31 (10.17)

我生活在一個綁包的世界裡

譴責 GJ 不通知有模擬賽,\(-1.5h\) 打模擬賽時間

\(\mathcal A\)

入機題,模擬即可

一開始還被求最大操作次數給詐騙了

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n;
char st[N];
int main()
{
	scanf("%s",st+1),n=strlen(st+1);
	int ans=0;
	for(int i=2;i<=n;i++)
	{
		int x=st[i-1]-'0'+st[i]-'0';
		if(x<=9) st[i]='0'+x,ans++;
			else st[i-1]='0'+x/10,st[i]='0'+x%10,ans++,i--;
	}
	printf("%d",ans);
	return 0;
}

\(\mathcal B\)

不僅 \(200\) 個點還綁包?

構造題,可參考 AT_abc358_f Easiest Maze,但不是完全一樣,還是有點差異的

上下界還是有一定難度想到,畢竟賽後才知道那個 \(2 \mid n, 2 \mid m\) 會使下界 \(+1\)

上界可以考慮直接螺轉,下界可以考慮弄一些豎線,然後橫著來一刀就差不多了

\(\mathcal C\)

表示式求值的方案數

甚至覺得比 [CSP-J 2022] 邏輯表示式 那題會簡單一點,根本不用考慮優先順序,全部運算似乎都會用一對 () 包著

開兩個棧,一個記 \(0\)\(1\) 的方案數,另一個記運算子,每遇到一個 ) 就計算一次貢獻即可

做完了,時間複雜度 \(\mathcal O(n)\)

可以不用像題解那樣建表示式樹

#include<bits/stdc++.h>
#define int long long
#define mp make_pair
#define PII pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=1e6+5,mod=998244353;
int n;
char st[N];
stack<PII> ans;
stack<char> opt;
int mul(int x,int y)
{
	return x*y%mod;
}
int add(int x,int y)
{
	return x+y>=mod?x+y-mod:x+y;
}
signed main()
{
	scanf("%s",st+1),n=strlen(st+1);
	for(int i=1;i<=n;i++)
	{
		if(st[i]=='0') ans.push(mp(1,0));
		if(st[i]=='1') ans.push(mp(0,1));
		if(st[i]=='?') ans.push(mp(1,1));
		if(st[i]=='&'||st[i]=='|'||st[i]=='^'||st[i]=='#') opt.push(st[i]);
		if(st[i]==')')
		{
			PII x=ans.top();ans.pop();
			PII y=ans.top();ans.pop();
			char op=opt.top();opt.pop();
			PII res={0,0};
			if(op=='&'||op=='#')
			{
				res.fi=add(add(res.fi,mul(x.fi,y.fi)),add(mul(x.fi,y.se),mul(x.se,y.fi)));
				res.se=add(res.se,mul(x.se,y.se));
			}
			if(op=='|'||op=='#')
			{
				res.fi=add(res.fi,mul(x.fi,y.fi));
				res.se=add(add(res.se,mul(x.fi,y.se)),add(mul(x.se,y.fi),mul(x.se,y.se)));
			}
			if(op=='^'||op=='#')
			{
				res.fi=add(res.fi,add(mul(x.fi,y.fi),mul(x.se,y.se)));
				res.se=add(res.se,add(mul(x.fi,y.se),mul(x.se,y.fi)));
			}
			ans.push(res);
		}
	}
	printf("%lld",ans.top().se);
	return 0;
}

\(\mathcal D\)

在二維平面上有 \(n\) 個點,第 \(i\) 個點 \((x_i,y_i)\) 有權值 \(w_i\)

可以進行若干次這樣的操作:選擇兩個點 \(u,v\),將 \(u\) 的權值一部分 \(\Delta w\) 加給 \(v\),但是要承受 \(d=\sqrt{(x_u-x_v)^2+(y_u-y_v)^2}\) 的損失,即兩點間的歐幾里得距離。也就是 \(w_u-= \Delta w,w_v+= \max(0,\Delta w-d)\)

現在你希望所有點中最小的權值的最大,並求出該值。

兩點間每操作一次,顯然全域性點權總和會減少,即 \(\sum w \gets \sum w - \sqrt{(x_u-x_v)^2+(y_u-y_v)^2}\),那麼兩點間顯然只會操作最多一次

進一步地,操作可以形成一棵樹或是森林,且同一個連通塊 \(\lvert V \rvert\) 內的最大值為 \(\frac{\sum_{u \in V} w_u - mst}{\lvert V \rvert }\),其中 \(mst\) 為連通塊 \(\lvert V \rvert\) 的最小生成樹,上界易證

那麼可以先狀壓求出每個連通塊的點權,再 dp 即可

時間複雜度 \(\mathcal O(2^n (n^2+n \log n))\)

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define double long double
using namespace std;
const int N=21,M=N*N;
int n,fa[N];
double dis[N][N],dp[(1<<16)+5];
struct dat
{
	int x,y;
	double w;
}a[N];
int cnt;
struct Edge
{
	int u,v;
	double w;
}e[M];
bool cmp(Edge a,Edge b)
{
	return a.w<b.w;
}
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
double distance(dat a,dat b)
{
	return sqrt(1.0*(a.x-b.x)*(a.x-b.x)+1.0*(a.y-b.y)*(a.y-b.y));
}
double Kruskal()
{
	int tot=0;double mst=0;
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(e+1,e+1+cnt,cmp);
	for(int i=1;i<=cnt;i++)
	{
		int x=find(e[i].u),y=find(e[i].v);
		if(x==y) continue;
		fa[y]=x,mst+=e[i].w;
		if(++tot==n-1) break;
	}
	return mst;
}
int main()
{
//	freopen("desert.in","r",stdin);
//	freopen("desert.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d%Lf",&a[i].x,&a[i].y,&a[i].w);
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++) dis[i][j]=distance(a[i],a[j]);
	int S=(1<<n)-1;
	for(int i=0;i<=S;i++)
	{
		double sum=0;int siz=0;cnt=0;
		for(int j=1;j<=n;j++)
			if(i&(1<<(j-1))) sum+=a[j].w,siz++;
		for(int j=1;j<n;j++)
			for(int k=j+1;k<=n;k++)
				if(i&(1<<(j-1))&&i&(1<<(k-1)))
					e[++cnt]=(Edge){j,k,dis[j][k]};
		double mst=Kruskal();
		dp[i]=(sum-mst)/siz;
	}
	for(int i=0;i<=S;i++)
		for(int j=i;j;j=(j-1)&i)
			dp[i]=max(dp[i],min(dp[j],dp[j^i]));
	printf("%.10Lf",dp[S]);
	return 0;
}
/*
3
0 0 10
2 0 5
0 5 8
*/

Round 32 (10.18)

又是綁包。。。

\(\mathcal A\)

詐騙題,經過 \(\mathcal O(\log k)\) 次操作之後每個數變為 \(2k\)\(2k+1\)

暴力列舉前 \(50\) 次總和即可,\(m>50\) 的基本都可以看做 \(m=50\)

\(\mathcal B\)

詐騙題,並且成功在賽時把我騙了

因為題目說保證有解且唯一,所以除了 \(s_0\) 會出現奇數次,其他會出現偶數次

所以直接輸出出現奇數次的字元,開個桶計數即可

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,len,cnt[31];
char st[N];
int main()
{
//	freopen("history.in","r",stdin);
//	freopen("history.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n<<1;i++)
	{
		scanf("%s",st+1),len=strlen(st+1);
		for(int j=1;j<=len;j++) cnt[st[j]-'a'+1]++;
	}
	scanf("%s",st+1),len=strlen(st+1);
	for(int i=1;i<=len;i++) cnt[st[i]-'a'+1]--;
	for(int i=1;i<=26;i++)
		if(cnt[i]&1) return putchar('a'+i-1),0;
}
/*
2
abcabc
a
abc
b
abcb
*/

\(\mathcal C\)

咕咕咕

\(\mathcal D\)

咕咕咕

Round ? (10.19)

GJ 設成了 IOI 賽制 😃

真的沒意思,沒到兩個小時就 AC 前三題了

前面 \(3\) 題比較簡單,甚至 \(T3\) 我都做過原了。。。

\(T4\) 沒看,puts("0"); 總司令 \(15\) 分走了

\(100+100+100+15=315\)打得最爽也是最高分的一集

\(\mathcal A\)

入機數學題

顯然對於每一個 \(b_i\) 取模,必有一個數 \(x\) 使得 \(\forall i \in [1,n],x \bmod b_i=b_i-1\)

那麼答案就為 \(ans=\sum (b_i-1)\)

\(\mathcal B\)

洛谷 P3106 [USACO14OPEN] Dueling GPSs S

考慮反向建邊,從 \(n\) 開始跑三次 Dijkstra做完了

\(\mathcal C\)

CF920F SUM and REPLACE

線段樹板題

考慮先用線性篩 \(\mathcal O(N)\) 預處理 \(d(i)\),然後每次暴力修改 \(a_i \gets d(a_i)\)

線段樹再記個區間最大值 \(maxl\),顯然當 \(maxl \leq 2\) 時就不用再往下遞迴了

應該是要勢能分析的,但是筆者不會,考慮到一個數被修改很少次就會變成 \(1\)\(2\),每個數最多會被修改 \(\mathcal O(\log n)\)

所以時間複雜度 \(O(n \log n)\)

同型別題目推薦:

  • CF438D The Child and Sequence
  • 洛谷 P4145 上帝造題的七分鐘 2 / 花神遊歷各國

\(\mathcal D\)

咕咕咕