The 2023 ICPC Asia Hong Kong Regional Programming Contest (The 1st Universal Cup, Stage 2:Hong Kong)
Preface
不知道VP什么就继续找往年的区域赛真题来打了
这场题挺合我们队口味的,开场2h就开出了5题(徐神110min的时候就过相对最难的C题),而且手上还有3个会写的题
最后中间虽然因为F题卡常(CF评测机太慢导致的)浪费了快1h时间,但索性时间剩余很多还是4h下班了
后面的题感觉都不太可做,遂光速撤退,直接被徐神带着4h出线了ORZ
A. TreeScript
题本身貌似不难,但需要一点阅读理解水平,徐神开场写的我题目都没看
#include <bits/stdc++.h>
std::vector<int> ch[200005];
int dfs(int i) {
if(ch[i].size() == 0) return 1;
if(ch[i].size() == 1) return dfs(ch[i][0]);
int mx = -1, smx = -1;
for(auto ch: ch[i]) {
int upd = dfs(ch);
if(upd > smx) smx = upd;
if(smx > mx) std::swap(smx, mx);
}
return std::max(smx + 1, mx);
}
int main() {
std::ios::sync_with_stdio(false);
int t; std::cin >> t; while(t--) {
int n, p; std::cin >> n;
for(int i = 0; i <= n; ++i) ch[i].clear();
for(int i = 1, p; i <= n; ++i) {
std::cin >> p;
ch[p].push_back(i);
}
std::cout << dfs(1) << char(10);
}
return 0;
}
B. Big Picture
算是个思博题,想到了就很简单的一个题
首先不难发现所有黑色区域必然连通,因此只要考虑白色区域的连通块数即可
注意到由于染色的性质,每个白色连通块必然有且仅有一个格子,满足这个格子的右侧和下侧的格子都是黑色的
因此我们只要计算出每个格子被染成黑色/白色的概率独立求解每个问题,复杂度\(O(nm)\)
#include <bits/stdc++.h>
constexpr int mod = 998244353;
int n, m;
int p[1001][1001], q[1001][1001];
inline void add(int &a, const int &b) {
if((a += b) >= mod) a -= mod;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin >> n >> m;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) {
std::cin >> p[i][j];
add(p[i][j], p[i][j - 1]);
}
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) {
std::cin >> q[i][j];
add(q[i][j], q[i - 1][j]);
}
int ans = 2;
for(int i = 1; i < n; ++i) for(int j = 1; j < m; ++j) {
int64_t t = int64_t(p[i][j - 1]) * q[i - 1][j] % mod;
t = t * (mod + p[i + 1][m] - p[i + 1][j - 1]) % mod;
t = t * (mod + q[n][j + 1] - q[i - 1][j + 1]) % mod;
add(ans, t);
}
std::cout << ans << char(10);
return 0;
}
C. Painting Grid
ORZ徐神单切高难度构造,纯纯带飞
首先无解的情况有两种,一种是\(nm\)为奇数;另一种是\(2^n>m\or 2^m>n\),这种情况根据鸽笼原理可知显然无解
否则不妨设\(m\)为偶数,然后用二进制分组的思路,构造每行0/1个数相同,且每行/每列都不同的情况
然后后面咋搞的我就不清楚了,当时被安排去写L和E了,最抽象的是我上了个厕所回来这题就过了
#include <bits/stdc++.h>
bool a[1000][1000];
void work(int n, int m) {
int hm = m >> 1, t = 0;
for(int i = 0; i < hm; ++i) a[0][i] = 0;
for(int i = hm; i < m; ++i) a[0][i] = 1;
for(int i = 1; (1 << i - 1) < hm; ++i) {
t = i;
for(int j = 0; j < hm; ++j) a[i][j] = (j >> (i - 1)) & 1;
for(int j = hm; j < m; ++j) a[i][j] = !a[i][j - hm];
}
t += 1;
if(m <= 20) {
std::set<int> s;
for(int i = 0; i < t; ++i) {
int k = 0;
for(int j = 0; j < m; ++j) k |= (a[i][j]) << j;
s.insert(k);
}
for(int i = 0; i < (1 << m) && t + 2 <= n; ++i) {
if(__builtin_popcount(i) == m / 2
|| s.find(i) != s.end()
|| s.find((1 << m) - 1 ^ i) != s.end()) continue;
s.insert(i);
s.insert((1 << m) - 1 ^ i);
for(int j = 0; j < m; ++j) a[t][j] = (i >> j) & 1;
for(int j = 0; j < m; ++j) a[t + 1][j] = !a[t][j];
t += 2;
}
for(int i = 0; i < (1 << m) && t < n; ++i) {
if(__builtin_popcount(i) != m / 2 || s.find(i) != s.end()) continue;
s.insert(i);
for(int j = 0; j < m; ++j) a[t][j] = (i >> j) & 1;
t += 1;
}
} else {
if(t == n) return ;
if(n - t & 1) {
for(int i = 0; i < hm; ++i) a[t][i] = 1;
for(int i = hm; i < m; ++i) a[t][i] = 0;
t += 1;
}
std::vector<bool> s(m);
while(t < n) {
for(int i = 0; i < m; ++i) a[t][i] = s[i];
t += 1;
for(int i = 0; i < m; ++i) a[t][i] = !s[i];
t += 1;
int i;
for(i = 0; s[i]; ++i) s[i] = 0;
s[i] = 1;
}
}
return ;
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) {
int n, m, type; std::cin >> n >> m;
if((n & 1) && (m & 1)) { std::cout << "NO\n"; continue; }
if(n < 20 && m > (1 << n) || m < 20 && n > (1 << m)) { std::cout << "NO\n"; continue; }
if(m & 1) std::swap(n, m), type = 1;
else type = 0;
work(n, m);
std::cout << "YES\n";
if(type) {
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j)
std::cout << a[j][i];
std::cout << char(10);
}
} else {
for(int i = 0; i < n; ++i) {
for(int j = 0; j < m; ++j)
std::cout << a[i][j];
std::cout << char(10);
}
}
}
return 0;
}
D. Shortest Path Query
很裸的一个题,看完题意就和祁神秒出了一个和题解一模一样的凸包做法
然后后面我滚去写F了,祁神和徐神讨论感觉这个题状态数bound不太住,就扔了没管了
后面一看题解妈的就是这么做,然后来个没有证明的定理说明这状态数是\(O(n^{\frac{5}{3}})\)的,我直接呃呃
等后面有时间再写吧
E. Goose, Goose, DUCK?
这题祁神和徐神Rush出了做法,然后祁神写了主部分,我跑上去写了个经典的多线段求并的东西,凑合起来轻松一遍过了这个题
大致做法就是当固定右端点\(r\)时,对于某个数,它dangerous
的左端点的取值范围是一个区间,这个可以用two pointers
扫出来
然后对于所有数的不合法左端点个数就是把这些线段的并集大小求出来,除此之外还有支持加入/删除一条线段的功能
由于加入/删除的操作总数是\(O(n)\)级别的,因此可以拿线段树维护,加入看作区间加\(1\),删除看作区间减\(1\)
因为任何时候都不会有负数出现,因此查询\(0\)的个数等价于求最小值以及最小值出现的个数,这个很容易用线段树处理
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
#define ft first
#define sd second
vector<int> hsh;
int gethsh(int x){return lower_bound(hsh.begin(), hsh.end(), x)-hsh.begin();}
const int N = 1e6+5;
int n, k, A[N];
int pos[N], nxt[N], cnt[N];
int bd[N], lst[N];
#define CI const int&
class Segment_Tree
{
private:
pii O[N<<2]; int tag[N<<2];
inline pii merge(const pii& A,const pii& B)
{
if (A.ft<B.ft) return A;
else if (A.ft>B.ft) return B;
return pii(A.ft,A.sd+B.sd);
}
inline void apply(CI now,CI mv)
{
O[now].ft+=mv; tag[now]+=mv;
}
inline void pushdown(CI now)
{
if (tag[now]) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=0;
}
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void build(TN)
{
O[now].ft=0; O[now].sd=r-l+1;
if (l==r) return; int mid=l+r>>1;
build(LS); build(RS);
}
inline void modify(CI beg,CI end,CI mv,TN)
{
if (beg>end) return; if (beg<=l&&r<=end) return apply(now,mv);
int mid=l+r>>1; pushdown(now);
if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS);
O[now]=merge(O[now<<1],O[now<<1|1]);
}
inline int query(void)
{
if (O[1].ft!=0) return 0; return O[1].sd;
}
#undef TN
#undef LS
#undef RS
}SEG;
#undef CI
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> k;
hsh.push_back(-1);
for (int i=1; i<=n; ++i) cin >> A[i], hsh.push_back(A[i]);
sort(hsh.begin(), hsh.end());
hsh.erase(unique(hsh.begin(), hsh.end()), hsh.end());
for (int i=1; i<(int)hsh.size(); ++i) pos[i]=-1;
for (int i=1; i<=n; ++i){
int &p = pos[gethsh(A[i])];
if (p>0) nxt[p]=i;
else bd[gethsh(A[i])]=i;
p = i;
}
for (int i=1; i<(int)hsh.size(); ++i) nxt[pos[i]]=-1;
long long ans=0;
SEG.build();
for (int i=1; i<=n; ++i){
int x = gethsh(A[i]);
++cnt[x];
if (k==cnt[x]){
SEG.modify(lst[x]+1, bd[x], 1);
}else if (cnt[x]>k){
cnt[x]=k;
SEG.modify(lst[x]+1, bd[x], -1);
lst[x]=bd[x]; bd[x]=nxt[bd[x]];
SEG.modify(lst[x]+1, bd[x], 1);
}
int res = SEG.query();
ans += (res-(n-i));
}
cout << ans << '\n';
return 0;
}
F. Sum of Numbers
最抽象的一集,究极无敌卡常,最后特判了小数据直接用long long
不用高精才过
这题思路很简单,假设确定了第\(i\)段的长度\(len_i\),则第\(i+1\)段的长度\(len_{i+1}\)必然满足\(|len_{i+1}-len_i|\le 1\),否则我们把长的那一段补过去一定更优
即确定了开头段的长度后,后面每段的选择只有\(3\)种,因此可以暴搜找出每段的长度,然后高精加起来找个最小的输出即可
然后写完就会发现喜提TLE,然后你可能需要用到包括但不限于以下这些优化方式:
- 压位高精,我直接把\(18\)位压在一起
- 状态剪枝,如果当前的\(\{len\}\)数组的极差\(\ge 3\)一定不优
- 小情况特判,当\(n\le 18\)时直接用
long long
不用高精
虽然思路不难,但还是很恶心,本来在QOJ上的话早就过了,在CF的老年机上慢的一批
#include<cstdio>
#include<iostream>
#include<vector>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef long long LL;
const int N=200005;
int t,n,k,len[10]; char s[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
#define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
public:
inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
Tp inline void write(T x,const char ch='\n')
{
RI ptop=0; while (pt[++ptop]=x%10,x/=10);
while (ptop) pc(pt[ptop--]+48); pc(ch);
}
inline void read_char(char& ch)
{
while (!isdigit(ch=tc()));
}
inline void put_char(const char& ch)
{
pc(ch);
}
inline void flush(void)
{
fwrite(Fout,1,Ftop-Fout,stdout);
}
#undef tc
#undef pc
}F;
struct BigInt
{
const static LL S=1e18;
vector <LL> num;
BigInt(CI l=-1,CI r=-1,CI res=0)
{
num.reserve(res);
num.clear(); if (l!=-1)
{
for (RI i=r,j;i>=l;i-=18)
{
LL x=0; for (j=max(i-17,l);j<=i;++j) x=x*10LL+s[j]-'0';
num.push_back(x);
}
}
}
inline int size(void) const { return num.size(); }
inline LL& operator [](CI x) { return num[x]; }
inline const LL& operator [](CI x) const { return num[x]; }
friend inline BigInt operator + (const BigInt& A,const BigInt& B)
{
BigInt C; RI i; LL carry=0;
if (A.size()<=B.size())
{
for (i=0;i<A.size();++i)
{
LL val=A[i]+B[i]+carry;
C.num.push_back(val%S); carry=val/S;
}
for (i=A.size();i<B.size();++i)
{
LL val=B[i]+carry;
C.num.push_back(val%S); carry=val/S;
}
if (carry) C.num.push_back(carry);
} else
{
for (i=0;i<B.size();++i)
{
LL val=A[i]+B[i]+carry;
C.num.push_back(val%S); carry=val/S;
}
for (i=B.size();i<A.size();++i)
{
LL val=A[i]+carry;
C.num.push_back(val%S); carry=val/S;
}
if (carry) C.num.push_back(carry);
}
return C;
}
friend inline bool operator < (const BigInt& A,const BigInt& B)
{
if (A.size()<B.size()) return 1;
if (A.size()>B.size()) return 0;
for (RI i=A.size()-1;i>=0;--i)
{
if (A[i]<B[i]) return 1;
if (A[i]>B[i]) return 0;
}
return 0;
}
inline void print(void)
{
RI i,j; vector <LL> vec; LL x=num.back();
while (x) vec.push_back(x%10),x/=10;
for (j=vec.size()-1;j>=0;--j) F.put_char(vec[j]+'0');
for (i=(int)num.size()-2;i>=0;--i)
{
LL x=num[i]; vec.clear();
for (j=0;j<18;++j) vec.push_back(x%10),x/=10;
for (j=vec.size()-1;j>=0;--j) F.put_char(vec[j]+'0');
}
}
}ans; LL rans;
inline void check(void)
{
if (n>18)
{
int lst=1; BigInt ret;
for (RI i=1;i<=k+1;++i)
ret=ret+BigInt(lst,lst+len[i]-1,n/k+5),lst+=len[i];
if (ret<ans) ans=ret;
} else
{
int lst=1; LL ret=0;
auto getint=[&](CI l,CI r)
{
LL ret=0; for (RI i=l;i<=r;++i) ret=ret*10LL+s[i]-'0'; return ret;
};
for (RI i=1;i<=k+1;++i)
ret=ret+getint(lst,lst+len[i]-1),lst+=len[i];
if (ret<rans) rans=ret;
}
}
inline void DFS(CI now,CI lst,CI sum,CI mn,CI mx)
{
if (sum>n) return;
if (mx-mn>=3) return;
if (now>k+1)
{
if (sum==n) check(); return;
}
for (RI i=-1;i<=1;++i) if (1<=lst+i&&lst+i<=n)
len[now]=lst+i,DFS(now+1,len[now],sum+len[now],min(mn,len[now]),max(mx,len[now]));
}
int main()
{
for (F.read(t);t;--t)
{
F.read(n); F.read(k); ans.num.resize(n+1); rans=1e18;
RI i; for (i=1;i<=n;++i) F.read_char(s[i]);
int L=(2*n-k*(k+1)+2*k+1)/(2*k+2),R=(2*n+k*(k+1))/(2*k+2);
for (i=max(1,L);i<=min(n,R);++i) len[1]=i,DFS(2,i,i,i,i);
if (n>18) ans.print(),F.put_char('\n'); else F.write(rans);
}
return F.flush(),0;
}
G. Paddle Star
神秘几何题,祁神和徐神讨论了半天发现样例都玩不出来,直接弃疗
H. Another Goose Goose Duck Problem
签到,答案就是\(\lceil\frac{a}{b}\rceil\times b\times k\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int l,r,b,k;
int main()
{
scanf("%d%d%d%d",&l,&r,&b,&k); int a=l;
return printf("%lld",1LL*(a+b-1)/b*b*k),0;
}
I. Range Closest Pair of Points Query
神秘的区间询问版最近点对,做不来一点
J. Dice Game
看题目名就知道是个神秘的Counting,直接弃疗
K. Maximum GCD
挺有意思的一个题
首先找到序列中最小的元素\(m\),分要对\(m\)操作和不对\(m\)操作两种情况进行讨论
如果不对\(m\)进行操作,则可以枚举\(m\)的约数\(t\),验证答案为\(t\)是否合法
如果一个数\(a_i\)原来不是\(t\)的倍数,那么最优的方案就是直接把\(a_i\)变成\(t\),但要注意取模操作能变成的数是有上界\(\lfloor\frac{a_i-1}{2}\rfloor\)的
否则要操作\(m\)的情况很容易证明可以取得最优值\(\lfloor\frac{m-1}{2}\rfloor\),综上找一个最优的情况输出即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],mn=1e9,ans;
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i)
scanf("%d",&a[i]),mn=min(mn,a[i]);
auto bound=[&](CI x)
{
return (x-1)/2;
};
auto check=[&](CI m)
{
for (RI i=1;i<=n;++i)
if (a[i]%m!=0&&bound(a[i])<m) return false;
return true;
};
for (ans=bound(mn),i=1;i*i<=mn;++i) if (mn%i==0)
{
if (check(i)) ans=max(ans,i);
if (check(mn/i)) ans=max(ans,mn/i);
}
return printf("%d",ans),0;
}
L. Permutation Compression
思路啥的都很自然的一个题
首先检验\(\{b_m\}\)是否是\(\{a_n\}\)的子序列,若不是就直接判掉
考虑从大到小考虑\(n\sim 1\)中的每个数\(i\),显然可以根据\(i\)是否要被删除来分类讨论:
- 若\(i\)不被删除,则后面的所有区间都不能经过\(i\)这个数,相当于将某个连续的可操作区间一分为二
- 若\(i\)要被删除,则考虑求出此时\(i\)所在的可操作区间的长度\(len\),显然我们要在所有\(\{\ell_i\}\)中找出\(\le len\)且最大的那个来删除\(i\)
用set
来维护所有连续的可操作区间,并用树状数组维护每个位置实际上是否被删除,用multiset
来求\(len\)的前驱,总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<set>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
struct ifo
{
int l,r,len;
inline ifo(CI L=0,CI R=0,CI LEN=0)
{
l=L; r=R; len=LEN;
}
friend inline bool operator < (const ifo& A,const ifo& B)
{
return A.l!=B.l?A.l<B.l:A.r<B.r;
}
}; int t,n,m,k,x,a[N],b[N],pos[N],ext[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
class Tree_Array
{
private:
int bit[N];
public:
#define lowbit(x) (x&-x)
inline void add(RI x,CI y)
{
for (;x<=n;x+=lowbit(x)) bit[x]+=y;
}
inline int get(RI x,int ret=0)
{
for (;x;x-=lowbit(x)) ret+=bit[x]; return ret;
}
inline void init(CI n)
{
for (RI i=1;i<=n;++i) bit[i]=0;
for (RI i=1;i<=n;++i) add(i,1);
}
#undef lowbit
}BIT;
int main()
{
for (F.read(t);t;--t)
{
RI i,j; for (F.read(n),F.read(m),F.read(k),i=1;i<=n;++i) F.read(a[i]);
for (i=1;i<=m;++i) F.read(b[i]); multiset <int> unused;
for (i=1;i<=k;++i) F.read(x),unused.insert(x);
bool flag=1; for (i=j=1;i<=m;++i)
{
while (j<=n&&a[j]!=b[i]) ++j;
if (j>n) { flag=0; break; }
}
if (!flag) { puts("NO"); continue; }
for (i=1;i<=n;++i) ext[i]=0,pos[a[i]]=i;
for (i=1;i<=m;++i) ext[b[i]]=1;
set <ifo> s; s.insert(ifo(1,n,n));
for (BIT.init(n),i=n;i>=1;--i)
if (ext[i])
{
auto it=s.lower_bound(ifo(pos[i],n+1,0)); --it;
int l=it->l,r=it->r; s.erase(it);
auto calc=[&](CI l,CI r)
{
return BIT.get(r)-BIT.get(l-1);
};
if (l<=pos[i]-1) s.insert(ifo(l,pos[i]-1,calc(l,pos[i]-1)));
if (pos[i]+1<=r) s.insert(ifo(pos[i]+1,r,calc(pos[i]+1,r)));
} else
{
auto it=s.lower_bound(ifo(pos[i],n+1,0)); --it;
auto itr=unused.upper_bound(it->len);
if (itr==unused.begin()) { flag=0; break; }
--itr; unused.erase(itr); BIT.add(pos[i],-1);
ifo tmp=ifo(it->l,it->r,it->len-1);
s.erase(it); s.insert(tmp);
}
puts(flag?"YES":"NO");
}
return 0;
}
Postscript
这么看来这场出线好像确实没啥难度的说,看看今年能不能找个冷门站点,发挥好点直接偷鸡