Preface
中秋加训,发现 Ucup 里好多比赛要么之前做过,要么就是太毒瘤(有场比赛直接把模 \(998244353\) 写在题目名里了),因此就直接跳到这场北美区决赛了
这场前期因为卡题意以及卡签到打的还是挺难受的,不过好在恶心归恶心题目还是都能出,最后也是堪堪写了 9 题
由于这场没找到题解(其实找到了,但好像要FQ)剩下的题就先坑着了
A. Allergen Testing
刚开始想的是如果只有一天就是经典的二进制分组模型,天数较多的话就考虑平均分一下,交上去发现 WA 飞了
后面徐神出手发现其实就是把原来的二进制编码改成 \(d+1\) 进制编码,因此答案就是 \(\lceil\log_{d+1}n\rceil\)
for _ in range(int(input())):
n, k = map(int, input().split())
ans = 0
k += 1
i = 1
while i < n:
ans += 1
i *= k
print(ans)
B. A Tree and Two Edges
大力分讨题
考虑先求出一个生成树,并将多出的两条边记为 \((a,b),(c,d)\)
不难发现答案的上界就是 \(4\),要单纯的人为区分 \(1,2,3,4\) 的 Case 很复杂,但我们可以枚举所有可能的路径并检验其是否合法
以 \(u\to a\to b\to c\to d\to v\) 的路径来举例,其实只要看 \(u\sim a,b\sim c,d\sim v\) 这三部分路径上的点是否有重复
检验的话也很简单,用树剖把路径拆成若干个区间,判断这些区间是否有交即可,直接排序后判断即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=50005;
int n,q,x,y,fa[N],a,b,c,d; vector <int> v[N];
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
namespace Tree_Division
{
int dep[N],dfn[N],sz[N],top[N],anc[N],son[N],idx;
inline void DFS1(CI now=1,CI fa=0)
{
sz[now]=1; dep[now]=dep[fa]+1; anc[now]=fa;
for (auto to:v[now]) if (to!=fa)
{
DFS1(to,now); sz[now]+=sz[to];
if (sz[to]>sz[son[now]]) son[now]=to;
}
}
inline void DFS2(CI now=1,CI topf=1)
{
dfn[now]=++idx; top[now]=topf;
if (son[now]) DFS2(son[now],topf);
for (auto to:v[now]) if (to!=anc[now]&&to!=son[now]) DFS2(to,to);
}
inline void modify(int x,int y,vector <pi>& Itv)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
Itv.push_back({dfn[top[x]],dfn[x]});
x=anc[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
Itv.push_back({dfn[y],dfn[x]});
}
};
using namespace Tree_Division;
inline bool check(vector <int> vec)
{
vector <pi> Itv;
for (RI i=0;i+1<vec.size();i+=2) modify(vec[i],vec[i+1],Itv);
sort(Itv.begin(),Itv.end());
for (RI i=0;i+1<Itv.size();++i)
if (Itv[i].se>=Itv[i+1].fi) return 0;
return 1;
}
int main()
{
scanf("%d%d",&n,&q);
for (RI i=1;i<=n;++i) fa[i]=i;
for (RI i=1;i<=n+1;++i)
{
scanf("%d%d",&x,&y);
if (getfa(x)==getfa(y))
{
if (a==0) a=x,b=y; else c=x,d=y;
continue;
}
v[x].push_back(y); v[y].push_back(x);
fa[getfa(x)]=getfa(y);
}
DFS1(); DFS2();
for (RI i=1;i<=q;++i)
{
scanf("%d%d",&x,&y); int ans=1;
ans+=check({x,a,b,y});
ans+=check({x,b,a,y});
ans+=check({x,c,d,y});
ans+=check({x,d,c,y});
ans+=check({x,a,b,c,d,y});
ans+=check({x,a,b,d,c,y});
ans+=check({x,b,a,c,d,y});
ans+=check({x,b,a,d,c,y});
ans+=check({x,c,d,a,b,y});
ans+=check({x,c,d,b,a,y});
ans+=check({x,d,c,a,b,y});
ans+=check({x,d,c,b,a,y});
printf("%d\n",ans);
}
return 0;
}
C. Broken Minimum Spanning Tree
考虑按照边权从小到大枚举所有的非树边 \((u,v,w)\),在树上遍历一遍找出 \(u\sim v\) 的路径上最长的边 \(w'\)
若 \(w<w'\) 则一定要发生替换,且由于我们枚举的顺序,当前替换了一定最优,所以直接模拟这个过程即可
#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#include<array>
#include<algorithm>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=3005;
int n,m,x[N],y[N],z[N],in[N],pre[N]; pi lst[N]; multiset <array <int,3>> v[N];
inline void addedge(CI id)
{
v[x[id]].insert({y[id],z[id],id});
v[y[id]].insert({x[id],z[id],id});
}
inline void deledge(CI id)
{
v[x[id]].erase(v[x[id]].find({y[id],z[id],id}));
v[y[id]].erase(v[y[id]].find({x[id],z[id],id}));
}
inline void find_path(CI now)
{
for (auto [to,w,id]:v[now]) if (pre[to]==0)
pre[to]=now,lst[to]={w,id},find_path(to);
}
int main()
{
scanf("%d%d",&n,&m);
vector <pi> E,ans;
for (RI i=1;i<=m;++i)
{
scanf("%d%d%d",&x[i],&y[i],&z[i]);
if (i<n) in[i]=1,addedge(i); else E.push_back({z[i],i});
}
sort(E.begin(),E.end());
for (auto [_,id]:E)
{
if (in[id]) continue;
for (RI i=1;i<=n;++i) pre[i]=0; pre[x[id]]=-1;
find_path(x[id]); pi mx={0,0};
for (int now=y[id];now!=x[id];now=pre[now]) mx=max(mx,lst[now]);
if (mx.fi>z[id])
{
ans.push_back({id,mx.se});
deledge(mx.se); in[mx.se]=0;
addedge(id); in[id]=1;
}
}
printf("%d\n",ans.size());
for (auto [x,y]:ans) printf("%d %d\n",x,y);
return 0;
}
F. Four Square
沟槽大力分讨题,大致思路就是先算出矩形的边长,然后考虑一定存在一个矩形 cover 了这个边长,递归处理即可
#include <bits/stdc++.h>
struct R { int h, w; };
using vii = std::vector<R>;
#define run(i) ({ std::cout << i << "\n"; return 0; })
bool solve(int h, int w, vii v) {
// std::cerr << "solve(" << h << ", " << w << ")\n";
if(h > w) std::swap(h, w);
if(h < 0) return false;
if(h == 0) return true;
auto fres = [&v] (int i, int j) {
vii res;
for(int k = 0; k < v.size(); ++k) if(k != i && k != j) res.emplace_back(v[k]);
return res;
};
for(int i = 0; i < v.size(); ++i) {
if(v[i].w > w) return false;
}
for(int i = 0; i < v.size(); ++i) {
if(v[i].w == w && solve(h - v[i].h, w, fres(i, -1))) return true;
if(v[i].w == h && solve(w - v[i].h, h, fres(i, -1))) return true;
if(v[i].h == w && solve(h - v[i].w, w, fres(i, -1))) return true;
if(v[i].h == h && solve(w - v[i].w, h, fres(i, -1))) return true;
}
for(int i = 0; i < v.size(); ++i) for(int j = i + 1; j < v.size(); ++j) {
if(v[i].w == v[j].w && v[i].h + v[j].h == w && solve(h - v[i].w, w, fres(i, j))) return true;
if(v[i].h == v[j].h && v[i].w + v[j].w == w && solve(h - v[i].h, w, fres(i, j))) return true;
if(v[i].w == v[j].h && v[i].h + v[j].w == w && solve(h - v[i].w, w, fres(i, j))) return true;
if(v[i].h == v[j].w && v[i].w + v[j].h == w && solve(h - v[i].h, w, fres(i, j))) return true;
if(v[i].w == v[j].w && v[i].h + v[j].h == h && solve(w - v[i].w, h, fres(i, j))) return true;
if(v[i].h == v[j].h && v[i].w + v[j].w == h && solve(w - v[i].h, h, fres(i, j))) return true;
if(v[i].w == v[j].h && v[i].h + v[j].w == h && solve(w - v[i].w, h, fres(i, j))) return true;
if(v[i].h == v[j].w && v[i].w + v[j].h == h && solve(w - v[i].h, h, fres(i, j))) return true;
}
return false;
}
int main() {
std::ios::sync_with_stdio(false);
int ss = 0, t;
vii rect(4, {0, 0});
for(auto &[h, w]: rect) {
std::cin >> h >> w;
if(h > w) std::swap(h, w);
ss += h * w;
}
t = std::sqrt(ss);
if(t * t != ss) run(0);
std::cout << solve(t, t, rect) << char(10);
return 0;
}
G. Frequent Flier
小清新贪心题
不难发现我们可以用一个类似滑动窗口的东西维护当前区间内选了多少数,若这个值大于等于 \(k\) 则可以先贪心地不操作
否则考虑在当前区间内的某些位置选数,显然这个位置是越靠后越好;但因为某个位置可能填到了上界,因此不得不转而选择前面的位置
用经典 trick,拿一个并查集来维护从每个位置往前第一个还可以填数的位置,势能分析一下会发现复杂度很对
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=400005;
int n,m,k,f[N],g[N],fa[N];
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&k);
for (RI i=1;i<=n;++i) scanf("%lld",&f[i]);
for (RI i=1;i<=n+m;++i) fa[i]=i;
int sum=0,ans=0;
for (RI i=1;i<=n+m-1;++i)
{
if (i-m>=1) sum-=g[i-m];
if (sum>=k) continue;
int x=getfa(i);
while (x>0&&x>i-m&&sum<k)
{
int tmp=min(f[x]-g[x],k-sum);
g[x]+=tmp; ans+=tmp; sum+=tmp;
if (f[x]==g[x]) fa[x]=getfa(x-1),x=getfa(x);
}
}
return printf("%lld",ans),0;
}
I. Power of Divisors
徐神开场写的签,我题意都不知道
from math import (pow, floor)
x = int(input())
if(x == 1):
print(x)
exit(0)
def f(n: int):
count = 0
i = 1
while i * i < n:
if n % i == 0:
count += 2
i += 1
if i * i == n: count += 1
return count
ans = 1_0000_0000_0000
for i in range(2, 64):
dx = floor(pow(x, 1.0 / i))
while dx ** i > x: dx -= 1
while (dx + 1) ** i <= x: dx += 1
if dx ** i != x: continue
if f(dx) == i:
ans = min(ans, dx)
print(-1 if ans == 1_0000_0000_0000 else ans)
J. Repetitive String Invention
很纯的 string 题,那当然是得让徐神 solo 了
#include <bits/stdc++.h>
constexpr int $n = 1605;
int go[$n][26], fail[$n], len[$n], las = 1, O = 1;
std::vector<int> occ[$n], ch[$n];
void insert(char c) {
c -= 'a';
int p = las, np = las = ++O;
len[np] = len[p] + 1;
memset(go[np], 0, sizeof(go[np]));
for(; p && !go[p][c]; p = fail[p]) go[p][c] = np;
if(!p) {
fail[np] = 1;
return ;
}
int q = go[p][c];
if(len[q] == len[p] + 1) {
fail[np] = q;
return ;
}
int nq = ++O;
memcpy(go[nq], go[q], sizeof(go[nq]));
len[nq] = len[p] + 1;
fail[nq] = fail[q];
fail[np] = fail[q] = nq;
for(; p && go[p][c] == q; p = fail[p]) go[p][c] = nq;
return ;
}
bool vis[$n][$n];
int hkr[$n][$n];
int n;
void dfs(int cur) {
for(auto ch: ch[cur]) {
dfs(ch);
for(int i = 1; i <= n; ++i) occ[cur][i] += occ[ch][i];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::string s; std::cin >> s;
n = s.size();
for(int i = 0; i < n; ++i) insert(s[i]);
for(int i = 1; i <= O; ++i) ch[fail[i]].emplace_back(i), occ[i].assign(n + 1, 0);
for(int i = 1, cur = 1; i <= n; ++i) {
cur = go[cur][s[i - 1] - 'a'];
occ[cur][i] = 1;
}
dfs(1);
for(int i = 1; i <= O; ++i) for(int j = 1; j <= n; ++j) occ[i][j] += occ[i][j - 1];
for(int l = 1; l <= n; ++l) for(int r = l, cur = 1; r <= n; ++r)
hkr[l][r] = cur = go[cur][s[r - 1] - 'a'];
int64_t ans = 0;
for(int l = 1; l <= n; ++l) {
for(int r = l, cur = 1; r <= n; ++r) {
cur = go[cur][s[r - 1] - 'a'];
assert(cur == hkr[l][r]);
// if(vis[cur][r - l + 1]) continue;
// vis[cur][r - l + 1] = 1;
// std::cerr << "l, r = " << l << ", " << r << char(10);
for(int k = r + (r - l + 1); k <= n; ++k) if(occ[cur][k] - occ[cur][k - 1] == 1) {
// std::cerr << " k = " << k << char(10);
ans += 1;
if(k == r + (r - l + 1)) {
continue;
}
int cl = r + 1, cr = k - (r - l + 1), cc = hkr[cl][cr];
// std::cerr << " cl, cr = " << cl << ", " << cr << char(10);
int prev = ans;
ans += occ[cc][l - 1];
if(k + (cr - cl) < n) ans += occ[cc][n] - occ[cc][k + (cr - cl + 1) - 1];
// std::cerr << " update = " << ans - prev << char(10);
}
}
}
std::cout << ans << char(10);
return 0;
}
K. Space Alignment
读懂题目就很简单的一个题
首先很容易找出每一行是括号嵌套的第几层,并且可以求出每一行空格和 Tab 的数量,记作一个二元组
那么对于同层的所有二元组,若它们全部相同,则无法推出有用信息;否则其实以及可以唯一确定答案的值了,接下来要做的就是带回去检验一下
如果单独靠每层的信息无法推出答案,则考虑将相邻层的二元组作差,然后进行和上面类似的操作即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<cstring>
#include<algorithm>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=105;
typedef pair <int,int> pi;
int n,a[N],dep[N],stk[N],top,mxdep; pi st[N]; vector <pi> bkt[N]; char s[1005];
inline bool check(CI k)
{
if (k<=0) return 0;
static int res[N];
for (RI i=0;i<=mxdep;++i)
{
vector <int> tmp;
for (auto [x,y]:bkt[i]) tmp.push_back(x+y*k);
sort(tmp.begin(),tmp.end());
tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
if (tmp.size()!=1) return 0;
res[i]=tmp.back();
}
if (mxdep==0) return 1;
vector <int> tmp;
for (RI i=1;i<=mxdep;++i)
tmp.push_back(res[i]-res[i-1]);
sort(tmp.begin(),tmp.end());
tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
if (tmp.size()!=1) return 0;
return tmp.back()>0;
}
int main()
{
scanf("%d",&n);
for (RI i=1;i<=n;++i)
{
scanf("%s",s+1);
int len=strlen(s+1); pi cur={0,0};
for (RI j=1;j<len;++j)
if (s[j]=='s') ++cur.fi; else ++cur.se;
st[i]=cur; a[i]=(s[len]=='{'?1:-1);
}
for (RI i=1;i<=n;++i)
{
if (a[i]==1) stk[++top]=i; else
{
int pre=stk[top--];
dep[i]=dep[pre]=top;
bkt[top].push_back(st[i]);
bkt[top].push_back(st[pre]);
mxdep=max(mxdep,top);
}
}
for (RI i=0;i<=mxdep;++i)
{
sort(bkt[i].begin(),bkt[i].end());
bkt[i].erase(unique(bkt[i].begin(),bkt[i].end()),bkt[i].end());
if (bkt[i].size()==1) continue;
auto [a,b]=bkt[i][0]; auto [c,d]=bkt[i][1];
if ((a==c&&b!=d)||(a!=c&&b==d)) return puts("-1"),0;
if (abs(c-a)%abs(b-d)!=0) return puts("-1"),0;
int k=(c-a)/(b-d);
if (check(k)) return printf("%d",k),0; else return puts("-1"),0;
}
if (mxdep==0) return puts("1"),0;
vector <pi> tmp;
for (RI i=1;i<=mxdep;++i)
{
auto [a,b]=bkt[i-1].back(); auto [c,d]=bkt[i].back();
tmp.push_back({c-a,d-b});
}
sort(tmp.begin(),tmp.end());
tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
if (tmp.size()==1)
{
if (check(1)) return puts("1"),0; else return puts("-1"),0;
}
auto [a,b]=tmp[0]; auto [c,d]=tmp[1];
if ((a==c&&b!=d)||(a!=c&&b==d)) return puts("-1"),0;
if (abs(c-a)%abs(b-d)!=0) return puts("-1"),0;
int k=(c-a)/(b-d);
if (check(k)) return printf("%d",k),0; else return puts("-1"),0;
return 0;
}
L. Splitting Pairs
遇到博弈直接打表启动,打了表后也是不难找到规律:
- 当 \(n\) 为偶数时,当且仅当所有 \(a_i\) 均为奇数时,先手必败;
- 当 \(n\) 为奇数时,当且仅当所有 \(\operatorname{lowbit}(a_i)\) 全相同时,先手必败;
具体证明可以用经典的镜像法则推导
#include<bits/stdc++.h>
using namespace std;
#define int long long
int lb(int x) {return x&(-x);}
int solve() {
int n; cin >> n;
vector<int> vec(n);
for (int i=0; i<n; ++i) cin >> vec[i];
if (n%2==0) {
for (int i=0; i<n; ++i) if (vec[i]%2==0) return 1;
return 0;
}
for (int i=1; i<n; ++i) {
if (lb(vec[0]) != lb(vec[i])) return 1;
}
return 0;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
int t; cin >> t; while (t--) cout << solve() << '\n';
return 0;
}
Postscript
美好的中秋假期就在一天网络赛,两天训练中结束了,真是团团又圆圆啊
标签:now,return,Cup,19,Universal,int,&&,include,id From: https://www.cnblogs.com/cjjsb/p/18417389