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