Codeforces Round 958 (Div. 2)

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

Preface

週末補題計劃的最後一場,這場由於是最古早的所以有些題題意都記不太清了

賽時經典發病,前四題一題 WA 一發,然後把時間打沒了 E 題經典沒寫完(中間還抽空寫了個假做法),郵電部詩人了屬於是


A. Split the Multiset

剛開始還感覺無從下手,寫了個記搜交上去還 WA 了,後面發現每次分裂出 \(1\) 就是最優的

#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=1005;
int t,n,k;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	scanf("%d%d",&n,&k),printf("%d\n",(n-1+k-2)/(k-1));
	return 0;
}

B. Make Majority

上來經典亂猜結論先掛一發後才能好好想題

不難發現操作一段區間如果得到的結果為 \(1\),則並不會使得 \(0,1\) 的個數差發生變化(如 \(101\to 1\),個數差保持在 \(1\) 個)

而唯一能減少個數差的操作只有把連續的一段 \(0\) 變成單獨一個,因此先這樣收縮完後比較 \(0,1\) 的個數即可

#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,pfx[N]; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d%s",&n,s+1); int c[2]={0,0};
		for (i=1;i<=n;++i)
		if (s[i]=='1') ++c[1]; else
		if (s[i-1]!='0') ++c[0];
		puts(c[1]>c[0]?"Yes":"No");
	}
	return 0;
}

C. Increasing Sequence with Fixed OR

經典對著樣例猜結論,透過觀察發現序列長度最大為 \(n\) 在二進位制下 \(1\) 的個數 \(+1\)

構造方案也很簡單,從高到低將為 \(1\) 的位依次消去即可,最後補上 \(n\) 本身

由於不能出現 \(0\),因此要特判形如 \(2^k\) 的數,當然關於這個為什麼是最優的建議去看 Tutorial

#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; LL n;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%lld",&n);
		vector <LL> ans;
		for (RI i=60;i>=0;--i)
		if (((n>>i)&1)&&(n^(1LL<<i))!=0) ans.push_back(n^(1LL<<i));
		ans.push_back(n);
		printf("%d\n",ans.size());
		for (auto x:ans) printf("%lld ",x); putchar('\n');
	}
	return 0;
}

D. The Omnipotent Monster Killer

經典先亂搞一發 WA 了再猜結論,我願稱之為紅溫做題法

剛開始一波觀察樣例認為只需要兩輪就能刪完,因此就是跑一個樹上最大權獨立集,然後寫完交上去就似了

後面手玩出了一組需要三輪操作的樣例,本來準備把兩輪改成三輪衝了,但後面還是冷靜想了想感覺會有更多輪的情況

但直覺告訴我們這個輪數一定不會很多,對著一個鏈的情況畫一畫會感覺大概就是 \(\log\) 級別的輪數,實現時就直接取 \(20\)

因此此時把問題轉化為,給樹上的每個點賦一個權值 \(c_i\in [1,20]\),在滿足任意兩個相鄰的點權值不同的情況下,最小化 \(\sum_{i=1}^n a_i\times c_i\)

很容易想到 DP,令 \(f_{i,j}\) 表示處理了 \(i\) 的子樹,且 \(c_i=j\) 時子樹內最小權值和,樸素的轉移是 \(O(n\times 20^2)\) 的,但預處理下每個點的最大/次大值可以很容易做到 \(O(n\times 20)\) 的複雜度

#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 int long long
#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=300005;
int t,n,a[N],x,y,f[N][20],mn[N],smn[N]; vector <int> v[N];
inline void DP(CI now=1,CI fa=0)
{
	RI i; for (i=1;i<20;++i) f[now][i]=a[now]*i;
	for (auto to:v[now]) if (to!=fa)
	{
		for (DP(to,now),i=1;i<20;++i)
		if (mn[to]==f[to][i]) f[now][i]+=smn[to];
		else f[now][i]+=mn[to];
	}
	mn[now]=f[now][1]; smn[now]=f[now][2];
	if (mn[now]>smn[now]) swap(mn[now],smn[now]);
	for (i=3;i<20;++i)
	if (f[now][i]<mn[now]) smn[now]=mn[now],mn[now]=f[now][i];
	else if (f[now][i]<smn[now]) smn[now]=f[now][i];
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; int sum=0; for (scanf("%d",&n),i=1;i<=n;++i)
		scanf("%lld",&a[i]),v[i].clear(),sum+=a[i];
		for (i=1;i<n;++i)
		scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
		DP(); printf("%lld\n",mn[1]);
	}
	return 0;
}

E. Range Minimum Sum

比賽的時候抽風了一個彎沒繞過了,然後就釋懷地似了

沒有刪除元素的時候做法很多,大致思路都是一致的,即找到每個位置左邊/右邊第一個小於該元素的位置 \(l_i/r_i\),實現時可以用單調棧(笛卡爾樹)或 set 處理

先計算出起始時的答案,並考慮修改一個位置 \(a_p\) 帶來的影響:

  • 最小值為 \(a_p\) 的區間沒了,這個貢獻初始時就已經計算過了;
  • 對於一些 \(a_i<a_p\) 的位置,以 \(i<p\) 為例,若 \([i+1,p-1]\) 中沒有 \(<a_i\) 的元素,則最小值為 \(a_i\) 的區間會少去一部分貢獻,這部分實際上是個區間加,可以差分解決;
  • 對於一些 \(a_i>a_p\) 的位置,以 \(i<p\) 為例,當 \(a_p\) 被刪掉後 \(a_i\) 為最小值的區間反而增加了;這類貢獻在 \(a_i\) 處統計比較方便,即我們找到 \(>i\) 的第一個 \(<a_i\) 的位置 \(p\),並找出 \(>p\) 的第一個 \(<a_i\) 的位置 \(q\),那麼 \(a_p\) 被刪除時最小值為 \(a_i\) 的區間就會增多 \((q-p-1)\times (i-l_i)\),另一個方向同理;

總複雜度 \(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 int long long
#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=500005;
int t,n,a[N],d[N],pos[N],dlt[N],val[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
		scanf("%lld",&a[i]),pos[a[i]]=i,d[i]=dlt[i]=0;
		set <int> s; s.insert(0); s.insert(n+1);
		int sum=0; for (i=1;i<=n;++i)
		{
			int p=pos[i],L=*(--s.lower_bound(p)),R=*s.upper_bound(p);
			val[p]=i*(p-L)*(R-p); sum+=val[p];
			auto upt=[&](CI l,CI r,CI mv)
			{
				d[l]+=mv; d[r+1]-=mv;
			};
			if (L+1<=p-1) upt(L+1,p-1,-i*(R-p));
			if (p+1<=R-1) upt(p+1,R-1,-i*(p-L));
			auto it=s.lower_bound(p);
			if (it!=s.begin())
			{
				if (--it!=s.begin())
				{
					auto pre=it; --pre;
					dlt[*it]+=i*(*it-*pre-1)*(R-p);
				}
			}
			it=s.upper_bound(p);
			if (it!=s.end())
			{
				auto nxt=it; ++nxt;
				if (nxt!=s.end())
				{
					dlt[*it]+=i*(*nxt-*it-1)*(p-L);
				}
			}
			s.insert(p);
		}
		for (i=1;i<=n;++i) d[i]+=d[i-1];
		for (i=1;i<=n;++i) printf("%lld%c",sum-val[i]+d[i]+dlt[i]," \n"[i==n]);
	}
	return 0;
}

Postscript

感覺最近的訓練強度有點大了,好多題都沒來得及補就要開始新一週的訓練了,不過還是很充實的說

相關文章