Codeforces Round #778 (Div. 1 + Div. 2)
CF1654A Maximum Cake Tastiness
显然是最大值和次大值和,因为我总可以把任两个数换到一起。
CF1654B Prefix Removals
首字母在后面出现过就一定会被删,否则一定不会。
CF1654C Alice and the Cake
很轻松可以求出原蛋糕大小,然后按题意模拟切糕,切出来的如果条件中有就匹配掉,否则继续切,切成 \(n\) 块后还没匹配完肯定无解。
const int MAXN = 2e5 + 5;
int a[MAXN];
multiset <LL> S;
int main() {
int T;read(T);
while(T -- > 0) {
int n;read(n);
LL num = 0;
S.clear();
for (int i = 1; i <= n; ++i) read(a[i]) , num += a[i] , S.insert(a[i]);
queue <LL> q;q.push(num);
int res = n - 1;
while(!q.empty()) {
LL cur = q.front();
q.pop();
auto it = S.lower_bound(cur);
if(it != S.end() && (*it) == cur) {
S.erase(it);
continue;
}
res --;if(res < 0) break;
q.push(cur / 2) , q.push((cur + 1) / 2);
}
if(res < 0) puts("NO");
else puts("YES");
}
return 0;
}
CF1654D Potion Brewing Class
线性筛,\(\log n\) 时间复杂度分解质因数,存在 set
里面模拟,算出 1
的最小值,然后模拟即可。
const int MAXN = 2e5 + 5;
const LL mod = 998244353;
int head[MAXN] , to[MAXN << 1] , nxt[MAXN << 1] , cnt;
pll edge[MAXN << 1];
void add(int u , int v , pll w) {nxt[++cnt] = head[u];head[u] = cnt;to[cnt] = v;edge[cnt] = w;}
int Is_pr[MAXN] , prim[MAXN] , num , Min[MAXN];
void pre() {
int N = 2e5;
for (int i = 2; i <= N; ++i) {
if(!Is_pr[i]) prim[++num] = i , Min[i] = i;
for (int j = 1; j <= num && prim[j] * i <= N; ++j) {
Is_pr[prim[j] * i] = 1;
Min[prim[j] * i] = prim[j];
if(i % prim[j] == 0) break;
}
}
}
int n;
map <LL , LL> cur , Ans;
LL qpow(LL a , LL b) {
LL res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void dfs(int x , int fa) {
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if(v == fa) continue;
LL X = edge[i].fs , y = edge[i].sc;
while(X > 1) {
LL tmp = Min[X];
LL tot = 0;
while(X % tmp == 0) X /= tmp , tot ++;
cur[tmp] -= tot;
}
while(y > 1) {
LL tmp = Min[y];
LL tot = 0;
while(y % tmp == 0) y /= tmp , tot ++;
cur[tmp] += tot;
Ans[tmp] = max(Ans[tmp] , cur[tmp]);
}
dfs(v , x);
X = edge[i].fs , y = edge[i].sc;
while(X > 1) {
LL tmp = Min[X];
LL tot = 0;
while(X % tmp == 0) X /= tmp , tot ++;
cur[tmp] += tot;
}
while(y > 1) {
LL tmp = Min[y];
LL tot = 0;
while(y % tmp == 0) y /= tmp , tot ++;
cur[tmp] -= tot;
}
}
}
LL ans;
void calc(int x , int fa , LL now) {
ans = (ans + now) % mod;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if(v == fa) continue;
calc(v , x , now * edge[i].fs % mod * qpow(edge[i].sc , mod - 2) % mod);
}
}
int main() {
int T;
read(T);
pre();
while(T -- > 0) {
read(n);
for (int i = 1; i < n; ++i) {
int u , v , x , y;
read(u),read(v),read(x),read(y);
add(u , v , mp(y , x));
add(v , u , mp(x , y));
}
cur.clear() , Ans.clear();
dfs(1 , 0);
LL res = 1;
for (auto it = Ans.begin(); it != Ans.end(); ++it) {
LL x = (it)->fs , y = (it)->sc;
if(y) res = res * qpow(x , y) % mod;
}
ans = 0;
calc(1 , 0 , res);
write(ans);
cnt = 0;
for (int i = 1; i <= n; ++i) head[i] = 0;
}
return 0;
}
CF1654E Arithmetic Operations
写了个大常数。
注意到值域的特殊性。
考虑枚举公差,如果公差很大,那么显然只会在 \(\frac{V}{d}\) 的范围内有数不变。因此根号分治,公差大的部分跑 dp
,公差小的依次枚举公差处理。
const int MAXN = 1e5 + 5;
const int N = 1e5;
const int S = 500;
int a[MAXN] , n;
unordered_map <int , int> dp[MAXN];
int cur[MAXN * S * 3];
int Abs(int x) {
return x < 0 ? -x:x;
}
int main() {
read(n);
for (int i = 1; i <= n; ++i) read(a[i]);
int ans = n;
for (int d = 0; d <= S; ++d) {
for (int i = 1; i <= n; ++i) cur[a[i] - d * i + N * S] ++ , ans = min(ans , n - cur[a[i] - d * i + N * S]);
for (int i = 1; i <= n; ++i) cur[a[i] - d * i + N * S] --;
for (int i = 1; i <= n; ++i) cur[a[i] + d * i + N * S] ++ , ans = min(ans , n - cur[a[i] + d * i + N * S]);
for (int i = 1; i <= n; ++i) cur[a[i] + d * i + N * S] --;
}
for (int i = n; i >= 1; --i) {
for (int j = 1; j <= S && i + j <= n; ++j) {
if((a[j + i] - a[i]) % j == 0) {
int d = (a[j + i] - a[i]) / j;
if(Abs(d) <= S) continue;
if(dp[j + i].count(d)) dp[i][d] = max(dp[i][d] , dp[j + i][d] + 1) , ans = min(ans , n - dp[i][d]);
else dp[i][d] = max(dp[i][d] , 2) , ans = min(ans , n - dp[i][d]);
}
}
}
write(ans);
return 0;
}
CF1654F Minimal String Xoration
见过的最易 F 题。
很容易想到倍增,模仿后缀排序。
理论依据是 \((i \oplus (j \oplus 2^k) = ((i \oplus 2^k) \oplus j)\)。(好蠢啊)
const int MAXN = (1 << 18) + 5;
char s[MAXN];
int rk[MAXN] , cnt[MAXN] , x[MAXN] , sa[MAXN];
int main() {
int n;
read(n);
scanf("%s" , s);
int m = 128;
for (int i = 0; i < (1 << n); ++i) cnt[rk[i] = s[i]] ++;
for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = 0; i < (1 << n); ++i) sa[cnt[rk[i]] --] = i;
for (int k = 0; k < n; ++k) {
int tot = 0;
for (int i = 1; i <= (1 << n); ++i) x[tot ++] = sa[i] ^ (1 << k);
for (int i = 0; i <= m; ++i) cnt[i] = 0;
for (int i = 0; i < (1 << n); ++i) cnt[rk[i]] ++;
for (int i = 0; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = tot - 1; i >= 0; --i) sa[cnt[rk[x[i]]] --] = x[i];
tot = 1;
swap(rk , x);
rk[sa[1]] = 1;
for (int i = 2; i <= (1 << n); ++i) {
if(x[sa[i]] == x[sa[i - 1]] && x[sa[i] ^ (1 << k)] == x[sa[i - 1] ^ (1 << k)]) rk[sa[i]] = tot;
else rk[sa[i]] = ++tot;
}
if(tot >= (1 << n)) break;
m = tot + 1;
}
for (int i = 0; i < (1 << n); ++i) putchar(s[sa[1] ^ i]);
return 0;
}
CF1654G Snowy Mountain
说实话,带点仙气。
首先是常规分析,需要先算出每个点到基地的最短距离,记为 \(h_x\)。
然后发现一个点最少都能走 \(h_x\) 步,最高也只能走 \(2h_x\) 步。
所以路径长度取决于我们能消耗多少势能,即够走多少平层。
容易想到,我们会在一个平层反复横跳以消耗掉富余势能。考虑加紧限制。
发现等价于我们只用在两个点上反复横跳即可达成目标,即我们要走到高度最低的一个平台去反复横跳,然后再一次性滚到基地。
得到了 \(O(n^2)\) 的做法。
接着便是神谕,考虑平台上的点 \(h_x\) 的和。
我们把平台的边断开,形成若干个连通块,然后把这些联通块拼回一号联通块,每次拼会增加一条边,和增加 \(2h_x\) ,且 \(h_x < siz\) ,\(siz\) 指新加连通块大小。
所以 \(h_x\) 的和是 \(O(n)\) 的。
因此 \(h_x\) 的种类数 \(O(\sqrt{n})\)。
对一种种类的平台,我们可以做一个 \(O(n)\) 的 \(dp\) 就可以了。
#include <map>
#include <set>
#include <queue>
#include <cmath>
#include <bitset>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
#define pii pair <int , int>
#define pll pair <LL , LL>
#define mp make_pair
#define fs first
#define sc second
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
//const int Mxdt=100000;
//static char buf[Mxdt],*p1=buf,*p2=buf;
//#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
template <typename T>
void read(T &x) {
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9') {if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
x *= f;
}
template <typename T>
void write(T x , char s='\n') {
if(!x) {putchar('0');putchar(s);return;}
if(x<0) {putchar('-');x=-x;}
T tmp[25]={},t=0;
while(x) tmp[t++]=x%10,x/=10;
while(t-->0) putchar(tmp[t]+'0');
putchar(s);
}
const int MAXN = 2e5 + 5;
int head[MAXN] , to[MAXN << 1] , nxt[MAXN << 1] , cnt;
void add(int u , int v) {nxt[++cnt] = head[u];head[u] = cnt;to[cnt] = v;}
int col[MAXN] , n , h[MAXN];
vector <int> P[MAXN];
int dp[MAXN] , vis[MAXN] , chk[MAXN] , ans[MAXN];
void dfs1(int x , int fa) {
vis[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if(v == fa || h[x] != h[v]) continue;
dp[v] = max(dp[v] , dp[x] - 1);
dfs1(v , x);
dp[x] = max(dp[x] , dp[v] - 1);
}
}
void dfs(int x , int fa) {
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if(v == fa) continue;
h[v] = min(h[v] , h[x] + 1);
dfs(v , x);
h[x] = min(h[x] , h[v] + 1);
}
}
int main() {
read(n);
for (int i = 1; i <= n; ++i) read(col[i]) , h[i] = ((int)1e9) * (!col[i]);
for (int i = 1; i < n; ++i) {
int u , v;
read(u),read(v);
add(u , v) , add(v , u);
}
dfs(1 , 0),dfs(1 , 0);
for (int i = 1; i <= n; ++i) P[h[i]].push_back(i);
vector <int> now;
for (int x = 1; x <= n; ++x) {
ans[x] = h[x];
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
chk[x] |= (h[x] == h[v]);
}
if(chk[x] && !vis[h[x]]) now.push_back(h[x]) , vis[h[x]] = 1;
}
sort(now.begin() , now.end());
for (auto p:now) {
for (int i = 1; i <= n; ++i)
dp[i] = (h[i] != p || !chk[i]) * (-1e9) , vis[i] = 0;
for (int w = p; w <= n; ++w) {
for (auto x:P[w]) {
if(!vis[x]) dfs1(x , 0),dfs1(x , 0);
}
for (auto x:P[w]) {
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if(h[v] <= h[x]) continue;
dp[v] = max(dp[v] , dp[x] + 1);
dp[v] = min(dp[v] , 0);
}
}
}
for (int i = 1; i <= n; ++i) if(!dp[i]) ans[i] = min(ans[i] , p);
}
for (int i = 1; i <= n; ++i) write(h[i] * 2 - ans[i] , ' ');
return 0;
}