Codeforces Round 953 (Div. 2)

空気力学の詩發表於2024-07-04

Preface

經典30min寫完前四題,然後E題大腦當機想複雜,最後寫了一坨很難除錯的東西成功把自己送走

趁著 Div1 的訓練還沒開始趕緊找回點狀態吧,不然到時候保準天天坑隊友的說


A. Alice and Books

不難發現 \(a_n\) 一定會取,那麼在剩下的裡面找一個最大的自成一堆就行

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=105;
int t,n,a[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		printf("%d\n",*max_element(a+1,a+n)+a[n]);
	}
	return 0;
}

B. New Bakery

把式子寫出來不難發現是個二次函式,可以直接解析求出最值,也可以直接寫三分求解

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
int t,n,a,b;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d%d",&n,&a,&b); int l=0,r=min(n,b);
		auto calc=[&](CI k)
		{
			return 1LL*(b+b-k+1)*k/2LL+1LL*a*(n-k);
		};
		while (r-l>2)
		{
			int lmid=l+(r-l)/3,rmid=r-(r-l)/3;
			if (calc(lmid)>=calc(rmid)) r=rmid; else l=lmid;
		}
		int pos=l; for (RI i=l+1;i<=r;++i)
		if (calc(i)>calc(pos)) pos=i;
		printf("%lld\n",calc(pos));
	}
	return 0;
}

C. Manhattan Permutations

首先不難發現 \(k\) 是奇數一定無解,否則我們考慮在 \([1,2,\dots,n]\) 的基礎上進行若干交換

手玩一下不難發現每次換最遠的沒交換過的兩個可以達到理論上界,而且可以構造出任意一箇中間值

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int t,n,p[N]; LL k;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%lld",&n,&k);
		if (k%2==1) { puts("No"); continue; }
		RI i,j,l; for (i=1;i<=n;++i) p[i]=i;
		for (i=1,j=n;i<j&&k>0;++i,--j)
		{
			int tmp=(j-i)*2;
			if (k>=tmp) k-=tmp,swap(p[i],p[j]); else
			{
				for (l=i+1;l<=j;++l)
				if ((l-i)*2==k) { k=0,swap(p[i],p[l]); break; }
			}
		}
		if (k>0) puts("No"); else
		{
			for (puts("Yes"),i=1;i<=n;++i) printf("%d%c",p[i]," \n"[i==n]);
		}
	}
	return 0;
}

D. Elections

感覺被我寫複雜了,摸了半天摸出來一坨,好在寫完就過了沒浪費多少時間

首先特判掉 \(1,n\) 兩個人的情況,並直接把剛開始多出來的 \(c\) 個人加到 \(a_1\)

對於 \(i\in [2,n-1]\),我們先找出 \(mx=\max_\limits{i<j\le n} a_j\),隨後進行討論:

  • \(mx>a_i\),此時無論如何 \([1,i-1]\) 的人都必須被幹掉,否則 \(a_i\) 沒法吃到閒散的票就不可能超過後面的人。算出此時 \(a_i\) 的值 \(a_i'\) 後再進行討論:
    • \(mx>a'_i\),則需要一次操作幹掉 \(mx\) 對應的人,此時局面顯然滿足要求
    • \(mx\le a'_i\),則不需要額外的操作
  • \(mx\le a_i\),此時不需要考慮後面的人,考慮把所有 \(j\in[1,i-1]\and a_j\ge a_i\) 的人都幹掉,之後又會出現兩種情況:
    • 幹掉的人對應的閒散票加到 \([1,i-1]\) 中第一個沒被幹掉的人上以後得到的值 \(< a_i\),此時不用進行額外的操作
    • 幹掉的人對應的閒散票加到 \([1,i-1]\) 中第一個沒被幹掉的人上以後得到的值 \(\ge a_i\),此時 \([1,i-1]\) 中的所有人都必須被幹掉

上述所有操作可以維護一個權值線段樹解決,總複雜度 \(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int t,n,c,a[N],rst[N],tot,suf[N],ans[N]; LL pfx[N];
class Segment_Tree
{
	private:
		int cnt[N<<2],mnp[N<<2]; LL sum[N<<2];
	public:
		#define TN CI now=1,CI l=1,CI r=tot
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			mnp[now]=n+1; cnt[now]=sum[now]=0;
			if (l==r) return; int mid=l+r>>1;
			build(LS); build(RS);
		}
		inline void updata(CI val,CI pos,TN)
		{
			if (l==r)
			{
				++cnt[now]; sum[now]+=rst[val];
				mnp[now]=min(mnp[now],pos);	return;
			}
			int mid=l+r>>1;
			if (val<=mid) updata(val,pos,LS); else updata(val,pos,RS);
			cnt[now]=cnt[now<<1]+cnt[now<<1|1];
			sum[now]=sum[now<<1]+sum[now<<1|1];
			mnp[now]=min(mnp[now<<1],mnp[now<<1|1]);
		}
		inline int query_cnt(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return cnt[now]; int mid=l+r>>1,ret=0;
			if (beg<=mid) ret+=query_cnt(beg,end,LS);
			if (end>mid) ret+=query_cnt(beg,end,RS);
			return ret;
		}
		inline LL query_sum(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return sum[now]; int mid=l+r>>1; LL ret=0;
			if (beg<=mid) ret+=query_sum(beg,end,LS);
			if (end>mid) ret+=query_sum(beg,end,RS);
			return ret;
		}
		inline int query_pos(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return mnp[now]; int mid=l+r>>1,ret=n+1;
			if (beg<=mid) ret=min(ret,query_pos(beg,end,LS));
			if (end>mid) ret=min(ret,query_pos(beg,end,RS));
			return ret;
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d%d",&n,&c); tot=0;
		for (i=1;i<=n;++i) scanf("%d",&a[i]);
		for (a[1]+=c,i=1;i<=n;++i) rst[++tot]=a[i];
		if (n==1) { puts("0"); continue; }
		for (suf[n]=a[n],i=n-1;i>=1;--i) suf[i]=max(suf[i+1],a[i]);
		for (i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
		sort(rst+1,rst+tot+1); tot=unique(rst+1,rst+tot+1)-rst-1;
		for (i=1;i<=n;++i) a[i]=lower_bound(rst+1,rst+tot+1,a[i])-rst;
		if (rst[a[1]]>=suf[2]) ans[1]=0; else ans[1]=1;
		for (ans[n]=0,i=1;i<n;++i) if (a[i]>=a[n]) { ans[n]=n-1; break; }
		for (SEG.build(),SEG.updata(a[1],1),i=2;i<n;++i)
		{
			if (suf[i+1]>rst[a[i]])
			{
				ans[i]=i-1; LL cur=rst[a[i]]+pfx[i-1];
				if (cur<suf[i+1]) ++ans[i];
			} else
			{
				ans[i]=SEG.query_cnt(a[i],tot);
				if (ans[i]<i-1&&(a[i]>1?rst[a[SEG.query_pos(1,a[i]-1)]]:0)+SEG.query_sum(a[i],tot)>=rst[a[i]]) ans[i]=i-1;
			}
			SEG.updata(a[i],i);
		}
		for (i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	}
	return 0;
}

E. Computing Machine

這題好像是個神秘的 \(O(n)\) ad hoc 題,但我完全沒想到神秘奇妙性質只感覺可以莫隊就直接衝了

首先不難發現先掃一遍序列 \(a\)\(b\) 儘量多的位置塗成 \(1\);然後回頭讓 \(b\)\(a\) 儘量多的位置塗成 \(1\);這個策略顯然是最優的

因此我們發現加入一個位置 \(i\) 後至多影響其周圍 \([i-3,i+3]\) 範圍內的貢獻,因此可以透過莫隊來處理

但寫的時候發現加入的情況還好說,刪除的情況怎麼寫都感覺漏Case,無奈之下只好去寫回滾莫隊這種不用刪除的科技

結構寫完發現由於上面講的一個位置的較大的影響範圍,還不能像傳統的回滾莫隊一樣直接滾回塊尾後面的位置

但我最擅長的就是魔改經典演算法了,後面發現我們手動放寬回滾莫隊中做暴力的區間閾值,使得右端點可以推到較遠的位置(程式碼裡取了 \(10\)

然後手動把會因為左端點移動帶來影響的前 \(3\) 個位置記錄下來,每次回滾左端點的時候一併復原即可

總複雜度 \(O(m\sqrt n)\),勝在不用動腦子找性質

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int blk[N];
struct ques
{
	int l,r,id;
	friend inline bool operator < (const ques& A,const ques& B)
	{
		return blk[A.l]!=blk[B.l]?blk[A.l]<blk[B.l]:A.r<B.r;
	}
}q[N]; int t,n,m,sz,ta[N],tb[N],ans[N]; char a[N],b[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; scanf("%d%s%s",&n,a+1,b+1);
		for (sz=(int)sqrt(n),i=1;i<=n;++i) blk[i]=(i-1)/sz+1;
		for (scanf("%d",&m),i=1;i<=m;++i)
		scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
		RI l=1,r=0,p=1; sort(q+1,q+m+1);
		for (i=1;i<=blk[n];++i)
		{
			int ret=0,lim=min(n,i*sz); r=lim;
			int tmp_ta[3],tmp_tb[3]; tmp_ta[0]=-1;
			while (p<=m&&blk[q[p].l]==i)
			{
				auto chk=[&](int& x,CI y)
				{
					if (x==0&&y==1) ++ret;
					if (x==1&&y==0) --ret;
					x=y;
				};
				auto addr=[&](CI r)
				{
					ta[r]=a[r]-'0'; tb[r]=b[r]-'0'; ret+=ta[r];
					if (r-2>=l)
					{
						if (tb[r]&&tb[r-2]) chk(ta[r-1],1);
						if (a[r]=='0'&&a[r-2]=='0') tb[r-1]=1;
						if (r-3>=l&&tb[r-1]&&tb[r-3]) chk(ta[r-2],1);
					}
				};
				auto addl=[&](CI l)
				{
					ta[l]=a[l]-'0'; tb[l]=b[l]-'0'; ret+=ta[l];
					if (l+2<=r)
					{
						if (tb[l]&&tb[l+2]) chk(ta[l+1],1);
						if (a[l]=='0'&&a[l+2]=='0') tb[l+1]=1;
						if (l+3<=r&&tb[l+1]&&tb[l+3]) chk(ta[l+2],1);
					}
				};
				auto BF=[&](CI l,CI r)
				{
					RI i; static int ta[N],tb[N];
					for (i=l;i<=r;++i) ta[i]=a[i]-'0',tb[i]=b[i]-'0';
					for (i=l;i+2<=r;++i) if (a[i]=='0'&&a[i+2]=='0') tb[i+1]=1;
					for (i=l;i+2<=r;++i) if (tb[i]&&tb[i+2]) ta[i+1]=1;
					int cur=0; for (i=l;i<=r;++i) cur+=ta[i]; return cur;
				};
				l=lim+1;
				if (q[p].r-q[p].l+1<=sz+10)
				{
					ans[q[p].id]=BF(q[p].l,q[p].r);
					++p; continue;
				}
				while (r<q[p].r) addr(++r);
				if (tmp_ta[0]==-1)
				{
					for (j=0;j<3;++j) tmp_ta[j]=ta[lim+1+j];
					for (j=0;j<3;++j) tmp_tb[j]=tb[lim+1+j];
				}
				int tmp=ret;
				while (l>q[p].l) addl(--l);
				ans[q[p++].id]=ret; ret=tmp;
				for (j=0;j<3;++j) ta[lim+1+j]=tmp_ta[j];
				for (j=0;j<3;++j) tb[lim+1+j]=tmp_tb[j];
			}
		}
		for (i=1;i<=m;++i) printf("%d\n",ans[i]);
	}
	return 0;
}

F. Large Graph

本來感覺沒啥突破口的,後面看到 \(k\ge 2\) 就知道是個傻逼題了

因為此時所有不為 \(1\) 的主對角線會直接連通在一起,因此原問題就轉化為 \(2n-1\) 條主對角線直接的連通性問題了

同時主對角線還有個很好的性質,就是我們從左到右對其編號後,則編號為 \(i\) 和編號為 \(j\) 的兩條對角線中任意選出兩個元素,它們的曼哈頓距離都為 \(|i-j|\)

利用上面兩個性質二維的問題就變成一維的情況了,看到 \(\gcd\) 就考慮分解質因數

對於某個質數 \(p\),不妨設其倍數對應的下標構成集合 \(pos_p\),則我們將 \(pos_p\) 中的元素排序後每次判斷相鄰的兩個元素能否連邊即可

用並查集暴力維護連通塊,總複雜度 \(O(n\log n\times \alpha (n))\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=2e6+5;
int t,n,k,a[N],pri[N],cnt,mnp[N],fa[N]; bool vis[N]; vector <int> pos[N];
inline void init(CI n)
{
	for (RI i=2,j;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i,mnp[i]=i;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		{
			vis[i*pri[j]]=1; mnp[i*pri[j]]=pri[j];
			if (i%pri[j]==0) break;
		}
	}
}
inline int getfa(CI x)
{
	return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t),init(1e6);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[n-1+i]);
		for (i=1;i<n;++i) a[i]=a[i+n]; vector <int> vec;
		for (i=1;i<=2*n-1;++i)
		{
			fa[i]=i; int x=a[i];
			while (x>1)
			{
				int p=mnp[x]; pos[p].push_back(i);
				vec.push_back(p); while (x%p==0) x/=p;
			}
		}
		sort(vec.begin(),vec.end());
		vec.erase(unique(vec.begin(),vec.end()),vec.end());
		for (auto p:vec)
		{
			for (i=0;i<pos[p].size()-1;++i)
			if (pos[p][i+1]-pos[p][i]<=k)
			fa[getfa(pos[p][i+1])]=getfa(pos[p][i]);
			pos[p].clear();
		}
		LL ans=0; for (i=1;i<=2*n-1;++i)
		{
			if (getfa(i)==i) ++ans;
			if (a[i]==1) ans+=(i<=n?i:2*n-i)-1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

Postscript

因為昨天這場E題寫的很唐再加上這兩天在幫徐神寫字串的題面,因此今天就只能補個部落格沒時間新開一場VP了,明天繼續堅持開一場吧

相關文章