The 9th CCPC (Harbin) Onsite(The 2nd Universal Cup. Stage 10: Harbin)
Preface
VP一下据说今年最水的CCPC,发现事实上也确实如此
3h左右Rush完6个题后发现竟然已经打进Au区了,然后开始EH双开一个不会,只能等徐神solo字符串
结果经典后2h啥也没干坐到结束,但看结果而已已经赢了太多
唉看来明年要好好研究下选赛站了,像今年Harbin和Guilin的拿奖难度完全不是一个level的啊
A. Go go Baron Bunny!
原神题,弃疗!
B. Memory
一个naive的想法是每次把之前的结果\(pre\)乘上\(\frac{1}{2}\)后再加上当前的\(a_i\),再判断其正负,但直接做精度会爆炸
但灵机一动会发现由于\(a_i\)是整数,因此要比较大小其实我们只关心有没有小数部分,因此直接拿个变量记录一下即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
const int N=100005;
int n,a[N];
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
int ret=0,flag=0; for (i=1;i<=n;++i)
{
auto sgn=[&](CI x)
{
return x>0?1:-1;
};
if (ret&1) flag=sgn(ret);
ret/=2; ret+=a[i];
if (ret==0&&flag==0) putchar('0'); else
if (ret>0||(ret==0&&flag>0)) putchar('+');
else putchar('-');
}
return 0;
}
C. Karshilov's Matching Problem II
唉string problem,直接扔给徐神然后摆烂
徐神最后写了个Z函数+莫队的奇怪东西,而且跳左右端点的复杂度竟然还不均衡
坐等徐神Debug.jpg
Upt:徐神已经补完力,没想到\(O(n\sqrt {n\log n})\)跑的还飞快
#include <bits/stdc++.h>
using llsi = long long signed int;
std::vector<int> get_z(const std::string &s) {
std::vector<int> z(s.size());
size_t n = z[0] = s.size();
for(int i = 1, l = -1, r = -1; i < n; ++i) {
if(i >= r) r = i;
else if(i + z[i - l] < r) {
z[i] = z[i - l];
continue;
}
ext: l = i;
while(r < n && s[r] == s[r - l]) r += 1;
z[i] = r - l;
}
return z;
}
std::vector<int> prefix_match(const std::string &s, const std::string &t) {
std::vector<int> z = get_z(t), res(s.size());
size_t n = s.size(), m = t.size();
for(int i = 0, l = -1, r = -1; i < n; ++i) {
if(i >= r) r = i;
else if(i + z[i - l] < r) {
res[i] = z[i - l];
continue;
}
ext: l = i;
while(r < n && r - l < m && s[r] == t[r - l]) r += 1;
res[i] = r - l;
}
return res;
}
std::vector<int> get_fail(const std::string &s) {
std::vector<int> fail(s.size());
size_t n = s.size();
fail[0] = -1;
for(int i = 1, j = -1; i < n; ++i) {
while(~j && s[j + 1] != s[i]) j = fail[j];
if(s[j + 1] == s[i]) j += 1;
fail[i] = j;
}
return fail;
}
std::vector<int> suffix_match(const std::string &s, const std::string &t) {
std::vector<int> fail = get_fail(t), res(s.size());
size_t n = s.size(), m = t.size();
for(int i = 0, j = -1; i < n; ++i) {
while(~j && t[j + 1] != s[i]) j = fail[j];
if(t[j + 1] == s[i]) j += 1;
res[i] = j + 1;
}
return res;
}
void work() {
int n, q, B;
std::cin >> n >> q;
B = std::sqrt(n * std::__lg(n));
std::string s, t;
std::cin >> s >> t;
std::vector<llsi> w(n), sw(n), fw(n), ans(q);
std::vector<int> top(n);
std::vector<int> pm = prefix_match(t, s);
std::vector<int> sm = suffix_match(t, s);
std::vector<int> f = get_fail(s);
std::vector<std::array<int, 3>> query(q);
std::vector<int> BB(n, 0);
for(int i = B; i < n; ++i) BB[i] = BB[i - B] + 1;
for(int i = 0; i < n; ++i) {
std::cin >> w[i];
sw[i] = fw[i] = w[i];
if(i > 0) sw[i] += sw[i - 1];
if(f[i] >= 0) fw[i] += fw[f[i]];
}
top[0] = -1;
for(int i = 1; i < n; ++i) {
if(f[i] < 0 || i - f[i] != f[i] - f[f[i]]) top[i] = f[i];
else top[i] = top[f[i]];
}
for(auto &[l, r, _]: query) std::cin >> l >> r, l -= 1, r -= 1;
for(int i = 0; i < q; ++i) query[i][2] = i;
std::sort(query.begin(), query.end(), [&](
const std::array<int, 3> &x,
const std::array<int, 3> &y
) {
auto [xl, xr, xi] = x;
auto [yl, yr, yi] = y;
return BB[xl] == BB[yl]
? xr < yr
: BB[xl] < BB[yl];
});
auto fetch_right = [&] (int l, int r) -> llsi {
int s = sm[r] - 1;
if(s < 0) return 0;
while(top[s] + 1 > r - l + 1) s = top[s];
if(s + 1 > r - l + 1) {
int dlt = s - f[s];
s -= (s - (r - l) + (dlt - 1)) / dlt * dlt;
}
return s < 0 ? 0 : fw[s];
};
int l = 0, r = -1;
llsi cans = 0;
for(auto [ql, qr, id]: query) {
while(l > ql) {
--l;
auto t = std::min(pm[l], r - l + 1);
cans += t > 0 ? sw[t - 1] : 0;
}
while(r < qr) {
++r;
cans += fetch_right(l, r);
}
while(l < ql) {
auto t = std::min(pm[l], r - l + 1);
cans -= t > 0 ? sw[t - 1] : 0;
l++;
}
while(r > qr) {
cans -= fetch_right(l, r);
r--;
}
ans[id] = cans;
}
for(int i = 0; i < q; ++i) std::cout << ans[i] << char(10);
return ;
}
int main() {
std::ios::sync_with_stdio(false);
int T = 1; while(T--) work();
return 0;
}
D. A Simple MST Problem
奇思妙想题
首先考虑如果区间内存在某个质数\(P\),则对于两个数\(x,y\),除非\(w(x)=w(\operatorname{LCM}(x,y))\)(即\(x\)对应的质因子集合为\(y\)对应的质因子集合的子集),否则不如用\(w(x)+1\)的代价直接把\(x\)和\(P\)连起来
因此现在的做法就很显然了,先把所有质因子集合有包含关系的点连起来,最后把每个连通块和\(P\)连起来即可
有一种比较好的处理方法是,对于某个数\(x\),我们令\(g(x)\)为它的质因数集合中所有数的乘积(由于有去重,因此\(g(12)=2\times 3=6;g(27)=3\))
此时\(x\)对应的质因子集合为\(y\)对应的质因子集合的子集等价于\(g(x)\)是\(g(y)\)的约数,那么直接在上面跑一个调和级数的枚举即可
令\(M=\sum r_i\),总复杂度\(O(M\log M)\)
但如果区间内没有质数怎么办呢,不难发现这样的区间长度一定不会很长,我们可以直接暴力跑生成树
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=1e6+5;
struct edge
{
int x,y,w;
inline edge(CI X=0,CI Y=0,CI W=0)
{
x=X; y=Y; w=W;
}
friend inline bool operator < (const edge& A,const edge& B)
{
return A.w<B.w;
}
}; int t,l,r,w[N],g[N],vis[N],sz[N],is_prime[N],fa[N];
inline void init(CI n)
{
RI i,j; for (i=1;i<=n;++i) g[i]=1;
for (i=2;i<=n;++i) if (!w[i])
{
is_prime[i]=1; g[i]=i; w[i]=1;
for (j=i*2;j<=n;j+=i) ++w[j],g[j]=g[j]*i;
}
}
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
for (scanf("%d",&t),init(1e6);t;--t)
{
RI i,j; scanf("%d%d",&l,&r); int ans=0;
if (l==1)
{
for (i=2;i<=r;++i) ans+=w[i];
printf("%d\n",ans); continue;
}
bool has_prime=0;
for (i=l;i<=r;++i) if (is_prime[i]) has_prime=1;
if (has_prime)
{
for (i=1;i<=r;++i) sz[i]=vis[i]=0;
for (i=l;i<=r;++i) ++sz[g[i]];
for (i=2;i<=r;++i) if (!vis[i]&&sz[i])
{
ans+=w[i]*(sz[i]-1)+(w[i]+1); vis[i]=1;
for (j=i*2;j<=r;j+=i) if (!vis[j]&&sz[j])
vis[j]=1,ans+=w[j]*sz[j];
}
printf("%d\n",ans-2);
} else
{
vector <edge> E; for (i=l;i<=r;++i) fa[i]=i;
for (i=l;i<=r;++i) for (j=l;j<=r;++j)
E.push_back(edge(i,j,w[i]+w[j]-w[__gcd(i,j)]));
sort(E.begin(),E.end());
for (auto [x,y,w]:E)
{
if (getfa(x)==getfa(y)) continue;
ans+=w; fa[getfa(x)]=getfa(y);
}
printf("%d\n",ans);
}
}
return 0;
}
E. Revenge on My Boss
看题解看的一头雾水的贪心题,祁神执意按自己理解Rush一个贪法然后寄了
Upt:祁神已经完全理解贪心思路并给出详细证明,ORZ
做法啥的由于官方题解中都讲了就不再赘述,这里补上官方题解省略的证明部分:
#include<bits/stdc++.h>
using namespace std;
#define int long long
using pii = pair<int, int>;
#define ft first
#define sd second
const int N = 1e5+5;
const int INF = (int)1e18+5;
int t, n, A[N], B[N], C[N], D[N];
int totB=0;
vector<int> ans;
bool check(int x){
ans.clear();
vector<pii> vecn, vecp;
for (int i=1; i<=n; ++i){
int e=x/C[i]-totB-A[i];
if (D[i]<=0) vecn.emplace_back(e, i);
else vecp.emplace_back(e+D[i], i);
}
// sort(vecn.begin(), vecn.end(), [&](const pii &a, const pii &b){return a>b;});
sort(vecn.begin(), vecn.end(), greater<pii>());
sort(vecp.begin(), vecp.end());
int pre=0;
// printf("check(%lld)\n", x);
// for (auto [e, pos] : vecn) printf("(%lld %lld)", e, D[pos]);
// for (auto [e, pos] : vecp) printf("(%lld %lld)", e-D[pos], D[pos]);
// puts("");
for (auto [e, pos] : vecn){
if (pre > e) return false;
ans.push_back(pos);
pre += D[pos];
}
for (auto [e, pos] : vecp){
if (pre > e-D[pos]) return false;
ans.push_back(pos);
pre += D[pos];
}
return true;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> t;
while (t--){
cin >> n;
totB=0;
for (int i=1; i<=n; ++i){
cin >> A[i] >> B[i] >> C[i];
D[i]=A[i]-B[i];
totB+=B[i];
}
int L=0, R=INF;
while (L<R){
int M = L+(R-L)/2;
if (check(M)) R=M;
else L=M+1;
}
// printf("L=%lld\n", L);
check(L);
for (int x : ans) cout << x << ' ';
cout << '\n';
}
return 0;
}
F. Palindrome Path
做不来捏
G. The Only Way to the Destination
由于题目中的墙总是竖着放的,因此很容易想到一列一列地考虑问题
先把一行内连续的空格位置一起考虑,如果对于相邻的两列,存在一个\(2\times 2\)的空格位置,那么显然是无解的
即等价于对于相邻的两列的空格位置,如果它们横坐标对应的区间相交的长度\(>1\),则一定无解
区间不交显然不会影响,现在只要考虑区间交长度为\(1\)的情况,不难发现由于会出现形如下图的情况(其中红色部分为墙,白色部分为空地)
此时不难发现空地连通块形成了环,因此也是无解的
考虑判掉这种情况,我们只需要把每列的白色区间看作一个点,然后把相邻的区间相交的长度\(=1\)的两个区间对应的点连边,最后看图中有没有环即可
建图的时候相邻两列连边的时候拿个two pointers
扫一下即可
#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
struct ifo
{
int l,r,id;
inline ifo(CI L=0,CI R=0,CI ID=0)
{
l=L; r=R; id=ID;
}
}; int n,m,k,idx,fa[N]; vector <ifo> v[N]; vector <pi> w[N],E;
inline int getfa(CI x)
{
return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
int main()
{
RI i; scanf("%d%d%d",&n,&m,&k);
if (n==1) return puts("YES"),0;
if (m>2*k+1) return puts("NO"),0;
for (i=1;i<=k;++i)
{
int x_1,x_2,y; scanf("%d%d%d",&x_1,&x_2,&y);
w[y].push_back(pi(x_1,x_2));
}
for (i=1;i<=m;++i)
{
sort(w[i].begin(),w[i].end());
int lst=0; for (auto [l,r]:w[i])
{
if (lst+1<=l-1) v[i].push_back(ifo(lst+1,l-1,++idx)); lst=r;
}
if (lst+1<=n) v[i].push_back(ifo(lst+1,n,++idx));
}
for (i=1;i+1<=m;++i)
{
auto interact=[&](const ifo& A,const ifo& B)
{
int L=max(A.l,B.l),R=min(A.r,B.r);
return max(0,R-L+1);
};
for (RI p=0,q=0;p<v[i].size();++p)
{
if (q>0) --q;
while (q>=0&&q<v[i+1].size()&&v[i+1][q].r<v[i][p].l) ++q;
while (q>=0&&q<v[i+1].size()&&interact(v[i][p],v[i+1][q])!=0)
{
if (interact(v[i][p],v[i+1][q])>1) return puts("NO"),0;
E.push_back(pi(v[i][p].id,v[i+1][q].id)); ++q;
}
}
}
for (i=1;i<=idx;++i) fa[i]=i;
//for (auto [x,y]:E) printf("%d %d\n",x,y);
for (auto [x,y]:E)
{
if (getfa(x)==getfa(y)) return puts("NO"),0;
fa[getfa(x)]=getfa(y);
}
return puts("YES"),0;
}
H. Energy Distribution
看了题解感觉是个没啥意思的题,就感觉它说的很对但没有那种让人恍然大悟的感觉
I. Rolling For Days
woc还有云批题的吗,但是完全做不来捏
J. Game on a Forest
经典博弈全靠猜
刚开始祁神搞了个什么势能分析之类的东西,然后推出来单棵树都是先手必胜的
结果后面一看\(n=4\)的链会有问题,然后发现原来有奇数个点的树和偶数个点的树还不太一样
手玩了一波后大胆猜测对于奇数个点的树,其\(SG=1\);而偶数个点的树,其\(SG=2\)
这样我们只需要判断删掉每个点/边后剩下的森林中有多少个点数为奇数的树,有多少个点数为偶数的树
删边的情况是trivial的,而删点的情况可以用换根DP求出删掉每个点后其子树的大小,总复杂度\(O(n)\)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, m, sz[N], odd=0, even=0;
int ans=0;
vector<int> G[N];
void dfs1(int x, int f){
sz[x]=1;
for (int v : G[x]){
if (v==f) continue;
dfs1(v, x);
sz[x]+=sz[v];
}
}
void dfs2(int x, int f, int out){
int o=0, e=0;
int tsz = sz[x];
for (int v : G[x]){
if (v==f) continue;
if (sz[v]%2==0) ++e; else ++o;
}
if (out>0){
int te=0, to=0;
if (sz[x]%2==0) ++te; else ++to;
if (out%2==0) ++te; else ++to;
if ((even+te)%2==0 && (odd+to)%2==0) ++ans;
if (out%2==0) ++e; else ++o;
}
if ((even+e)%2==0 && (odd+o)%2==0) ++ans;
for (int v : G[x]){
if (v==f) continue;
dfs2(v, x, out+sz[x]-sz[v]);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int a, b; cin >> a >> b;
G[a].push_back(b); G[b].push_back(a);
}
vector<int> rt;
for (int i=1; i<=n; ++i) if (0==sz[i]){
dfs1(i, -1);
rt.push_back(i);
if (sz[i]%2==0) ++even; else ++odd;
}
for (int x : rt){
if (sz[x]%2==0) --even; else --odd;
dfs2(x, -1, 0);
if (sz[x]%2==0) ++even; else ++odd;
}
cout << ans << '\n';
return 0;
}
K. Omniscia Spares None
唉又是原神,还是启动不了捏
L. Palm Island
徐神开场写的签到,我题目都没看过
#include <bits/stdc++.h>
short n, a[1000], b[1000];
std::vector<int> ans;
void op1() {
ans.push_back(1);
int t = a[0];
memcpy(a, a + 1, sizeof(short) * (n - 1));
a[n - 1] = t;
}
void op2() {
ans.push_back(2);
int t = a[1];
memcpy(a + 1, a + 2, sizeof(short) * (n - 2));
a[n - 1] = t;
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T;
while(T--) {
std::cin >> n;
ans.clear();
for(int i = 0; i < n; ++i) std::cin >> a[i], a[i] -= 1;
for(int i = 0; i < n; ++i) std::cin >> b[i], b[i] -= 1;
for(int i = 1; i < n; ++i) {
while(a[0] != b[i ]) op1();
while(a[n - 1] != b[i - 1]) op2();
}
while(a[0] != b[0]) op1();
for(int i = 0; i < ans.size(); ++i) std::cout << ans[i];
std::cout << char(10);
}
return 0;
}
M. Painter
模拟题,直接枚举询问的每个位置然后\(O(n)\)判断它被赋上了什么字符即可
但要注意题目中对于行列的定义和正常的大不相同(题目中是直角坐标系下的,原点在左下角)
#include<cstdio>
#include<iostream>
#include<vector>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
struct ifo
{
int tp,a,b,c,d; char w;
inline ifo(void)
{
tp=a=b=c=d=w=0;
}
}; vector <ifo> O; int n;
int main()
{
ios::sync_with_stdio(false); cin.tie(0);
for (cin>>n;n;--n)
{
string opt; cin>>opt;
if (opt=="Circle")
{
O.push_back(ifo());
cin>>O.back().b>>O.back().a>>O.back().c>>O.back().w;
} else
if (opt=="Rectangle")
{
O.push_back(ifo()); O.back().tp=1;
cin>>O.back().b>>O.back().a>>O.back().d>>O.back().c>>O.back().w;
} else
{
int x_1,y_1,x_2,y_2; cin>>x_1>>y_1>>x_2>>y_2;
for (RI i=y_2;i>=y_1;--i) for (RI j=x_1;j<=x_2;++j)
{
bool flag=0;
for (RI k=O.size()-1;k>=0;--k)
{
if (O[k].tp==0)
{
auto sqr=[&](CI x)
{
return 1LL*x*x;
};
if (sqr(i-O[k].a)+sqr(j-O[k].b)<=sqr(O[k].c))
{
putchar(O[k].w); flag=1; break;
}
} else
{
if (O[k].a<=i&&i<=O[k].c&&O[k].b<=j&&j<=O[k].d)
{
putchar(O[k].w); flag=1; break;
}
}
}
if (!flag) putchar('.');
if (j==x_2) putchar('\n');
}
}
}
return 0;
}
Postscript
明天徐神好像有点事,而且晚上也有CF Div1,小小休假一天