The 2023 ICPC Asia Xi'an Regional Contest
Preface
久违地组队训练一场,不知道打什么就挑了场最近才上 QOJ 的 23 年西安 Regional
作为“声名远扬”的凹包场,还有 \(O(\frac{n^3}{\omega})\) 过 \(n=5000\) 的神秘数据,导致这场的 downvote 率奇高
但上 QOJ 的时候数据应该是修复了的,凹包好像 fix 了,bitset
大力出奇迹的题我们队最后 1min 胡了个神秘讨论上去然后直接过了
最后 7 题罚时尾,话说最近不管什么比赛罚时怎么都这么炸
A. An Easy Geometry Problem
发病了,本来早过了,结果发病了线段树 pushup
能写挂直接花了快 2h 来调这个题,还浪费时间写了个拍才看出来
将 \(A_{i+r}-A_{i-r}=kr+b\) 稍加变形,得到 \(A_{i+r}-\frac{k}{2}\times (i+r)=A_{i-r}-\frac{k}{2}\times (i-r)+b\)
因此我们令 \(a_i=2\times A_i-i\times k,b_i=2\times A_i-i\times k+2\times b\),则比较是就是判断 \(a[i+1,i+r]\) (从前往后)和 \(b[i-r,i-1]\) (从后往前)是否相同
拿个线段树维护下区间向前/向后两个方向的哈希值即可,查询的话直接先二分再在线段树上查,实测再加个双哈希都能跑过
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod1=998244353,mod2=1e9+7,S=4e8;
int n,q,k,b,a[N],A[N],B[N];
struct Hasher
{
int x,y;
inline Hasher(CI X=0,CI Y=0)
{
x=X; y=Y;
}
friend inline bool operator == (const Hasher& A,const Hasher& B)
{
return A.x==B.x&&A.y==B.y;
}
friend inline bool operator != (const Hasher& A,const Hasher& B)
{
return A.x!=B.x||A.y!=B.y;
}
friend inline Hasher operator + (const Hasher& A,const Hasher& B)
{
return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
}
friend inline Hasher operator - (const Hasher& A,const Hasher& B)
{
return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
}
friend inline Hasher operator * (const Hasher& A,const Hasher& B)
{
return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
}
}pw[N],sum[N];
const Hasher seed=Hasher(2*S+31,2*S+131);
struct ifo
{
Hasher val; int len;
inline ifo(const Hasher& VAL=Hasher(),CI LEN=0)
{
val=VAL; len=LEN;
}
};
inline ifo merge_pre(const ifo& L,const ifo& R)
{
return ifo(L.val*pw[R.len]+R.val,L.len+R.len);
}
inline ifo merge_suf(const ifo& L,const ifo& R)
{
return ifo(R.val*pw[L.len]+L.val,L.len+R.len);
}
class Segment_Tree
{
private:
ifo pre[N<<2],suf[N<<2]; Hasher tag[N<<2];
#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 apply(CI now,const Hasher& mv)
{
tag[now]=tag[now]+mv;
pre[now].val=pre[now].val+mv*sum[pre[now].len-1];
suf[now].val=suf[now].val+mv*sum[suf[now].len-1];
}
inline void pushup(CI now)
{
pre[now]=merge_pre(pre[now<<1],pre[now<<1|1]);
suf[now]=merge_suf(suf[now<<1],suf[now<<1|1]);
}
inline void pushdown(CI now)
{
if (tag[now]!=Hasher()) apply(now<<1,tag[now]),apply(now<<1|1,tag[now]),tag[now]=Hasher();
}
public:
inline void build(TN)
{
pre[now].len=suf[now].len=r-l+1;
if (l==r)
{
pre[now].val=Hasher((A[l]+mod1)%mod1,(A[l]+mod2)%mod2);
suf[now].val=Hasher((B[l]+mod1)%mod1,(B[l]+mod2)%mod2);
return;
}
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline void modify(CI beg,CI end,const Hasher& mv,TN)
{
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);
pushup(now);
}
inline ifo qry_pre(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return pre[now]; int mid=l+r>>1; pushdown(now);
if (end<=mid) return qry_pre(beg,end,LS);
if (beg>mid) return qry_pre(beg,end,RS);
return merge_pre(qry_pre(beg,end,LS),qry_pre(beg,end,RS));
}
inline ifo qry_suf(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return suf[now];
int mid=l+r>>1; pushdown(now);
if (end<=mid) return qry_suf(beg,end,LS);
if (beg>mid) return qry_suf(beg,end,RS);
return merge_suf(qry_suf(beg,end,LS),qry_suf(beg,end,RS));
}
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
// freopen("A.in","r",stdin); freopen("A.out","w",stdout);
scanf("%d%d%d%d",&n,&q,&k,&b);
for (RI i=1;i<=n;++i)
{
scanf("%d",&a[i]);
A[i]=2*a[i]-k*i;
B[i]=2*a[i]-k*i+2*b;
}
pw[0]=Hasher(1,1); sum[0]=Hasher(1,1);
for (RI i=1;i<=n;++i) pw[i]=pw[i-1]*seed;
for (RI i=1;i<=n;++i) sum[i]=sum[i-1]+pw[i];
SEG.build();
while (q--)
{
int opt; scanf("%d",&opt);
if (opt==1)
{
int l,r,v; scanf("%d%d%d",&l,&r,&v);
SEG.modify(l,r,Hasher((2*v+mod1)%mod1,(2*v+mod2)%mod2));
} else
{
int x; scanf("%d",&x);
int l=1,r=min(x-1,n-x),ret=0;
while (l<=r)
{
int mid=l+r>>1;
if (SEG.qry_pre(x+1,x+mid).val==SEG.qry_suf(x-mid,x-1).val) ret=mid,l=mid+1; else r=mid-1;
}
printf("%d\n",ret);
}
// for (RI i=1;i<=n;++i) printf("%d%c",SEG.qry_pre(i,i).val.x," \n"[i==n]);
// for (RI i=1;i<=n;++i) printf("%d%c",SEG.qry_suf(i,i).val.x," \n"[i==n]);
}
return 0;
}
E. Dominating Point
我勒个 4h59min 绝杀啊,感觉找到切入点就不难的一个题
考虑找到图中出度最多的点 \(A\),由竞赛图的性质,这个点一定是 Dominating Point
,具体证明可以看官方题解
此时不妨把剩下的点分为两个集合,\(A\) 出边的点集记为 \(S_1\),\(A\) 入边的点集记为 \(S_2\)
考虑若 \(S_2\) 为空则显然无解,否则我们把 \(S_2\) 中剩下的点的导出子图中,出度最多的点 \(B\) 找出,显然这个点也一定是 Dominating Point
现在问题就是第三个 Dominating Point
怎么找,题解给出的方法需要讨论 \(B\) 是否是中心点,但当时最后徐神告诉我令 \(S_3\) 为 \(B\) 入边的点集,则 \(S_3\) 中剩下的点的导出子图中,出度最多的点 \(C\) 一定合法
具体证明可以看官方题解,这题主要是画画图讨论一下就不难猜到结论
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005;
int n,deg[N],vis[N],key[N]; char s[N][N];
int main()
{
scanf("%d",&n);
for (RI i=1;i<=n;++i) scanf("%s",s[i]+1);
for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j)
if (s[i][j]=='1') ++deg[i];
int A=1,B=-1,C=-1; for (RI i=2;i<=n;++i) if (deg[i]>deg[A]) A=i;
if (deg[A]==n-1) return puts("NOT FOUND"),0;
for (RI i=1;i<=n;++i) if (s[i][A]=='1') vis[i]=1,B=i;
if (B==-1) return puts("NOT FOUND"),0;
for (RI i=1;i<=n;++i) deg[i]=0;
for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j)
if (vis[i]&&vis[j]&&s[i][j]=='1') ++deg[i];
for (RI i=1;i<=n;++i) if (vis[i]&°[i]>deg[B]) B=i;
for (RI i=1;i<=n;++i) if (s[i][B]=='1') key[i]=1,C=i;
for (RI i=1;i<=n;++i) deg[i]=0;
for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j)
if (key[i]&&key[j]&&s[i][j]=='1') ++deg[i];
for (RI i=1;i<=n;++i) if (key[i]&°[i]>deg[C]) C=i;
if (C==-1) return puts("NOT FOUND"),0;
return printf("%d %d %d",A,B,C),0;
}
F. An Easy Counting Problem
这题我基本没参与全程队友想+写的
做法大致就是通过卢卡斯定理,会推出一个类似于背包的转移形式
但由于数据范围出的比较 shit,因此需要先找原根把下标乘法转化为下标加法,然后用 NTT 优化循环卷积
#include <bits/stdc++.h>
using llsi = long long signed int;
constexpr llsi mod = 998244353;
int C[5000][5000];
llsi hkr[5000], ug[20000], ssk[20000], vv[20000];
void prep(int n);
void mult(llsi *a, llsi *b, int);
void NTT(llsi*, int);
int Log[5000], iLog[5000];
int find_root(int p) {
int i;
for(i = 1; i < p; ++i) {
int cur = i, logCur = 1;
while(cur != 1) cur = cur * i % p, logCur += 1;
if(logCur == p - 1) break;
}
assert(i < p); // impossible
int cur = 1, logCur = 0;
do {
iLog[Log[cur] = logCur] = cur;
cur = cur * i % p;
logCur += 1;
} while(cur != 1);
return i;
}
void work() {
std::string k;
int p, x;
std::cin >> k >> p >> x;
for(int i = 0; i < p; ++i) hkr[C[i][0] = 1]++;
for(int i = 1; i < p; ++i) for(int j = 1; j <= i; ++j)
hkr[C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % p]++;
// std::cerr << "root = " << find_root(p) << char(10);
find_root(p);
for(int i = 1; i < p; ++i) ug[Log[i]] = hkr[i];
// for(int i = 0; i < p; ++i) std::cout << hkr[i] << char(i == p - 1 ? 10 : 32);
// for(int i = 0; i < p - 1; ++i) std::cout << ug[i] << char(i == p - 2 ? 10 : 32);
prep(2 * (p - 1));
std::reverse(k.begin(), k.end());
ssk[0] = 1;
for(auto ch: k) {
if(ch == '1') mult(ssk, ug, p - 1);
mult(ug, ug, p - 1);
}
// for(int i = 0; i < p - 1; ++i) std::cout << ssk[i] << char(i == p - 2 ? 10 : 32);
std::cout << ssk[Log[x]] << char(10);
}
int main() {
std::ios::sync_with_stdio(false);
int n; n = 1; while(n--) work();
return 0;
}
int n, otae[20000];
constexpr llsi ksm(llsi a, llsi b) {
llsi c = 1;
while(b) {
if(b & 1) c = c * a % mod;
a = a * a % mod;
b >>= 1;
}
return c;
}
constexpr llsi g = 3, gi = ksm(g, mod - 2);
void prep(int _n) {
n = std::bit_ceil(uint32_t(_n));
otae[0] = 0;
for(int i = 1; i < n; ++i)
otae[i] = (otae[i >> 1] >> 1) | ((n >> 1) & -(i & 1));
}
void NTT(llsi *a, int on) {
for(int i = 0; i < n; ++i) if(otae[i] > i) std::swap(a[i], a[otae[i]]);
for(int d = 1; d < n; d <<= 1) {
llsi W = ksm(on > 0 ? g : gi, (mod - 1) / (d << 1));
for(int j = 0; j < n; j += 2 * d) {
llsi w = 1;
for(int k = 0; k < d; ++k, w = w * W % mod) {
llsi x = a[j + k], y = w * a[j + d + k] % mod;
a[j + k] = (x + y) % mod;
a[j + d + k] = (mod + x - y) % mod;
}
}
}
if(on < 0) {
llsi inv = ksm(n, mod - 2);
for(int i = 0; i < n; ++i) a[i] = a[i] * inv % mod;
}
}
void mult(llsi *a, llsi *_b, int p) {
llsi *b = vv;
memcpy(b, _b, sizeof(llsi) * n);
// for(int i = p; i < n; ++i) b[i] = 0;
NTT(a, 1), NTT(b, 1);
for(int i = 0; i < n; ++i) a[i] = a[i] * b[i] % mod;
NTT(a, -1);
for(int i = n - 1; i >= p; --i) a[i - p] = (a[i] + a[i - p]) % mod, a[i] = 0;
}
G. An Easy Math Problem
签到题,本来现场暴力可过的,然后我们 VP 的时候被 extra test 干掉了,最后 3h 才会正解
考虑枚举 \(n\) 的一对约数 \(p,q\),显然为了去重我们只在 \(\gcd(p,q)=1\) 时计数
考虑将 \(n\) 质因数分解,令 \(n=\prod p_i^{c_i}\),不妨考虑 \(p_i\) 单个质因数的贡献
由于 \(p,q\) 互质,因此 \(p_i\) 的指数上二者取值的 \(\min\) 必须为 \(0\),因此有 \(2c_i+1\) 种方案
在不考虑 \(p\le q\) 时方案数显然为 \(\prod (2c_i+1)\),而有序对的答案为 \(\frac{\prod (2c_i+1)+1}{2}\)
#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,n;
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld",&n); int ans=1;
for (RI p=2;p*p<=n;++p)
if (n%p==0)
{
int c=0;
while (n%p==0) ++c,n/=p;
ans*=(2LL*c+1);
}
if (n>1) ans*=3;
printf("%lld\n",(ans+1)/2LL);
}
return 0;
}
H. Elimination Series Once More
暴力枚举每个人最多能赢的轮次,其实就是看当前某个区间内小于某个数的数量
从小到大处理每个 \(\{a_i\}\),然后拿一个类似线段树的东西维护下每层的区间和即可,复杂度 \(O(2^n\times n)\)
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=(1<<20)+5;
int n,k,all,a[N],pos[N],ans[N],sum[N<<2];
inline void update(vector <int>& vec,CI pos,CI now=1,CI l=1,CI r=all)
{
++sum[now]; vec.push_back(sum[now]);
if (l==r) return; int mid=l+r>>1;
if (pos<=mid) update(vec,pos,now<<1,l,mid); else update(vec,pos,now<<1|1,mid+1,r);
}
int main()
{
scanf("%d%d",&n,&k); all=1<<n;
for (RI i=1;i<=all;++i) scanf("%d",&a[i]),pos[a[i]]=i;
for (RI i=1;i<=all;++i)
{
int x=pos[i]; vector <int> vec;
update(vec,x); reverse(vec.begin(),vec.end());
// printf("i = %d\n",i);
// for (auto x:vec) printf("%d ",x); putchar('\n');
int ret=n;
for (RI r=0;r<vec.size();++r)
{
if ((1<<r)<=i&&(1<<r)-vec[r]<=k) continue;
ret=r-1; break;
}
ans[x]=ret;
}
for (RI i=1;i<=all;++i) printf("%d ",ans[i]);
return 0;
}
I. Max GCD
赛后看题解补的,感觉还是挺小清新的
考虑固定答案时,有意义的三元组 \((i,j,k)\) 一定满足 \(i,j\) 作为其倍数出现的位置相邻,\(k\) 是符合要求且最靠近 \(j\) 的位置,这样的三元组个数是 \(O(n\times \sigma(a_i))\) 的
如何快速求出这些三元组呢,枚举中间的值 \(a_i\) 的所有约数 \(d\),设上一次出现 \(d\) 的倍数的位置为 \(lst_d\),则所有 \(k\ge 2\times i-lst_d\) 的位置中,第一个满足 \(a_k\) 是 \(d\) 的倍数就是需要的
把信息挂在 \(2\times i-lst_d\) 上然后从后往前扫描一遍即可求出所有三元组,然后把询问离线跑一个二维数点即可
注意这题修改有 \(O(n\times \sigma(a_i))\) 次但查询只有 \(O(q)\) 次,因此拿个分块维护信息可以做到 \(O(n\times \sigma(a_i)+q\times \sqrt n)\) 的复杂度
#include<cstdio>
#include<iostream>
#include<vector>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
typedef pair <int,int> pi;
int n,q,a[N],lst[N],ans[N],sz,bel[N],val[N],tag[N];
vector <int> frac[N]; vector <pi> vec[N],mdy[N],ques[N];
inline void init(CI n)
{
for (RI i=1;i<=n;++i) for (RI j=i;j<=n;j+=i) frac[j].push_back(i);
}
inline void modify(CI p,CI mv)
{
val[p]=max(val[p],mv);
tag[bel[p]]=max(tag[bel[p]],mv);
}
inline int query(CI l,CI r)
{
int ret=0;
if (bel[l]==bel[r])
{
for (RI i=l;i<=r;++i) ret=max(ret,val[i]);
return ret;
}
for (RI i=l;i<=bel[l]*sz;++i) ret=max(ret,val[i]);
for (RI i=(bel[r]-1)*sz+1;i<=r;++i) ret=max(ret,val[i]);
for (RI i=bel[l]+1;i<=bel[r]-1;++i) ret=max(ret,tag[i]);
return ret;
}
int main()
{
scanf("%d%d",&n,&q); init(1e6);
for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
for (RI i=1;i<=n;++i)
{
for (auto d:frac[a[i]])
{
if (!lst[d]) continue;
vec[2*i-lst[d]].push_back({lst[d],d});
}
for (auto d:frac[a[i]]) lst[d]=i;
}
for (RI i=1;i<=1000000;++i) lst[i]=0;
for (RI i=n;i>=1;--i)
{
for (auto d:frac[a[i]]) lst[d]=i;
for (auto [l,g]:vec[i])
{
if (!lst[g]) continue;
mdy[lst[g]].push_back({l,g});
}
}
// for (RI i=1;i<=n;++i) for (auto [l,g]:mdy[i])
// printf("l = %d, r = %d, g = %d\n",l,i,g);
for (RI i=1;i<=q;++i)
{
int l,r; scanf("%d%d",&l,&r);
ques[r].push_back({l,i});
}
sz=(int)sqrt(n);
for (RI i=1;i<=n;++i) bel[i]=(i-1)/sz+1;
for (RI i=1;i<=n;++i)
{
for (auto [l,g]:mdy[i]) modify(l,g);
for (auto [l,id]:ques[i]) ans[id]=query(l,i);
}
for (RI i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
L. Prism Palace
祁神写的几何,我题目都没看
#include<bits/stdc++.h>
using namespace std;
#define int long long
using LD = long double;
const LD PI = acosl(-1.0L);
struct Pt {
int x, y;
int crs(const Pt &b) const {return x*b.y-y*b.x;}
int dot(const Pt &b) const {return x*b.x+y*b.y;}
Pt operator-(const Pt &b) const {return Pt{x-b.x, y-b.y};}
int quad() const {
if (x>0 && y>=0) return 1;
if (x<=0 && y>0) return 2;
if (x<0 && y<=0) return 3;
if (x>=0 && y<0) return 4;
return 0;
}
bool operator<(const Pt &b) const {
int qda = quad(), qdb = b.quad();
return (qda!=qdb ? qda < qdb : crs(b)>=0);
}
};
const int N = 2e5+5;
int n;
Pt pt[N], vv[N];
signed main() {
ios::sync_with_stdio(0); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(10);
cin >> n;
for (int i=0; i<n; ++i) {
int x, y; cin >> x >> y;
pt[i] = {x, y};
}
if (n==3) {
cout << 1.0L << '\n';
return 0;
}
for (int i=0; i<n; ++i) vv[i] = pt[i] - pt[(i-1+n)%n];
sort(vv, vv+n);
LD ans = 0.0L;
for (int i=0; i<n; ++i) {
Pt v1 = vv[(i-1+n)%n];
Pt v2 = vv[(i+1)%n];
if (v2.crs(v1) > 0) {
LD th = atan2l(v2.crs(v1), v2.dot(v1));
ans = (PI-th) / PI;
break;
}
}
cout << ans << '\n';
return 0;
}
N. Python Program
小清新模拟,难点在于处理输入
由于数据范围不大,我们第一层循环枚举,第二层循环推一个等差数列求和的式子即可
#include<cstdio>
#include<iostream>
#include<string>
#include<cctype>
#define RI register int
#define CI const int&
using namespace std;
const int INF=1e9;
int a,b,c,d,e,f;
int main()
{
string s; getline(cin,s); getline(cin,s);
int p=0; while (s[p]!='(') ++p; ++p;
while (isdigit(s[p])) a=a*10+s[p++]-'0'; ++p;
while (isdigit(s[p])) b=b*10+s[p++]-'0'; ++p;
if (s[p]==':') c=1; else
{
int flag=1; if (s[p]=='-') ++p,flag=-1;
while (isdigit(s[p])) c=c*10+s[p++]-'0'; ++p;
c*=flag;
}
getline(cin,s); p=0;
while (s[p]!='(') ++p; ++p;
if (isalpha(s[p])) d=INF,p+=2; else
{
while (isdigit(s[p])) d=d*10+s[p++]-'0'; ++p;
}
if (isalpha(s[p])) e=INF,p+=2; else
{
while (isdigit(s[p])) e=e*10+s[p++]-'0'; ++p;
}
if (s[p]==':') f=1; else
if (isalpha(s[p])) f=INF,p+=2; else
{
int flag=1; if (s[p]=='-') ++p,flag=-1;
while (isdigit(s[p])) f=f*10+s[p++]-'0'; ++p;
f*=flag;
}
getline(cin,s); long long ans=0;
// printf("%d %d %d\n%d %d %d\n",a,b,c,d,e,f);
auto calc=[&](CI a,CI b,CI c)
{
// printf("calc(%d,%d,%d) = ",a,b,c);
if (c>0)
{
if (a>=b) return 0LL;
int k=max((b-a-1)/c,0);
// printf("%lld\n",1LL*(2*a+1LL*k*c)*(k+1)/2LL);
return 1LL*(2*a+1LL*k*c)*(k+1)/2LL;
} else
{
if (a<=b) return 0LL;
int k=max((a-b-1)/(-c),0);
// printf("%lld\n",1LL*(2*a+1LL*k*c)*(k+1)/2LL);
return 1LL*(2*a+1LL*k*c)*(k+1)/2LL;
}
};
if (c>0)
{
for (RI i=a;i<b;i+=c)
if (d==INF) ans+=calc(i,e,f); else
if (e==INF) ans+=calc(d,i,f); else
if (f==INF) ans+=calc(d,e,i); else
ans+=calc(d,e,f);
} else
{
for (RI i=a;i>b;i+=c)
if (d==INF) ans+=calc(i,e,f); else
if (e==INF) ans+=calc(d,i,f); else
if (f==INF) ans+=calc(d,e,i); else
ans+=calc(d,e,f);
}
printf("%lld",ans);
return 0;
}
Postscript
今天举行的南京站同校已经有队伍校排 Rank10 进入出线队列了,这下感觉压力有点大啊