24暑假赛训合集
本文同步自 24暑期赛训合集
谢谢,你关注的鸽子博主更新了。
上赛季末段没能忍住网瘾, 转生成 ACMer 了
和队友一起拿了块邀请赛金牌和省赛冠军,下半年区域赛不想拖后腿所以还是得努努力啊。
但是因为博主还要跑科研实验 以及 机器人比赛的事情,所以大概一天只能看几个题
唉,被自己菜晕,能不能来点作用,别浪费队友开出来的题。
下列列出的 √ 为自己想出来的,× 为看了题解。
个人训练赛记录,CF / AT 为主
| 比赛 | sloved problem | rank |
|---|---|---|
| Codeforces Round #956 (Div. 2) and ByteRace 2024 | 4/7 | 802 |
| Codeforces Round 958 (Div. 2) | 3/6 | 1190 |
| Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2) | 5/8 | 806 |
| Codeforces Round 960 (Div. 2) | 4/7 | 368 |
wanna_be_free 训练赛记录,排名以 vp 榜为主
| 比赛 | sloved problem | rank |
|---|---|---|
| 2024 (ICPC) Jiangxi Provincial Contest | 10/12 | 10 |
| The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest | 12/13 | 5 |
| 2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site sloved problem(这场暂时先不写记录了吧,来不及写完了,回来补这场的时候再写) | 6/13 | 106 |
| 24牛客多校第一场 | 7/11 | 14 |
| 24牛客多校第二场 | 7/10 | 40 |
| 24牛客多校第三场 | 6/12 | 70 |
| 24牛客多校第四场 | 9/12 | 35 |
| 24牛客多校第五场 | 5/13 | 49 |
| 24牛客多校第六场 | 7/11 | 32 |
| 24牛客多校第七场 | 5/12 | 27 |
| 24牛客多校第八场 | 5/11 | 82 |
[√] 不知名题
求 \(p_i = (a p_{i - 1} ^ 2 + b p_{i - 1} + c) \mod m + \lfloor \frac{p_{i - 1}}{2} \rfloor\)
\(p_0 = k\) 时,求 \(p_{T}\)
\(k,T \leq 1e18\)
\(a,b,c,m \leq 5e5\)
看这个 \(m\) 显然就很能操作啊。
思考 \(p_i \leq 2m\) 时显然最后范围也在 \(2m\) 中,\(m\) 不大显然可以处理值域上的步数信息。
\(p_i > 2m\) 时,理论上 \(log_2{m}\) 步就进入范围了。
所以直接就倍增即可。
//明剑照霜,秋风走马
#include<bits/stdc++.h>
#define ll long long
#define M 600005
ll mod,a,b,c;
int T;
int A[M * 2][80];
ll mul[80];
signed main(){
// freopen("5.in","r",stdin);
// freopen("5.out","w",stdout);
scanf("%lld%lld%lld%lld",&mod,&a,&b,&c);
a = a % mod;
b = b % mod;
c = c % mod;
mul[0] = 1;
for(int i = 1;i <= 60;++i)mul[i] = mul[i - 1] * 2;
for(int i = 0;i <= 2 * mod;++i){A[i][0] = (1ll * a * (i % mod) * (i % mod) % mod+ 1ll * b * (i % mod) % mod + c) % mod + i / 2;}
for(int t = 1;t <= 60;++t){
for(int i = 0;i <= 2 * mod;++i){
A[i][t] = A[A[i][t - 1]][t - 1];
}
}
scanf("%lld",&T);
while(T -- ){
ll k,y;
scanf("%lld%lld",&k,&y);
while((k > 2 * mod) && y){--y;k = (1ll * a * (k % mod) * (k % mod) % mod + 1ll * b * (k % mod) % mod + c) % mod + k / 2;}
if(y == 0){std::cout<<k<<"\n";continue;}
ll now = k;
for(int t = 60;t >= 0;--t){if(y < 0)return 0;if(y >= mul[t]){now = A[now][t];y -= mul[t];}if(!y)break;}
std::cout<<now<<"\n";
}
}
另外 ll 求余 int 居然是 UB 吗,,,,
[√] [Usaco2008 Jan]猜数游戏
给若干区间 \([l,r]\) ,说明其最小值为 \(x\)
问最多前几个区间能够在数组每个都不一样的条件下自洽
首先第一眼看出来了能不能判全局合法,考虑每个相同权值的区间求交,由于每个数不一样则这个最小值一定出现在交集内,若没有交集则错
然后考虑从大权值加到小权值,若一个小权值的区间,在之前已经被大权值区间全部覆盖,则这个区间无法填数 (因为大权值限制条件显然更强)
然后想错了,上来写了个吉司机,一个区间一个区间加入,获得了 6.0 / 100.0 的好成绩
然后既然能判全局合法的方法,那我们直接二分 M 即可。
//山桃红花满上头,蜀江春水拍山流。
#include<bits/stdc++.h>
#define N 2000005
int t[N << 2];
int sum[N << 2];
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
#define tag(x) t[x]
#define s(x) sum[x]
#define mid ((l + r) >> 1)
int n,q;
#define root 1,1,n
inline void push(int u,int l,int r){if(tag(u) == -1)return ;tag(ls(u)) = tag(rs(u)) = tag(u);s(u) = (r - l + 1) * tag(u);s(ls(u)) = (mid - l + 1) * tag(u);s(rs(u)) = (r - mid) * tag(u);tag(u) = -1;}
inline void up(int u){s(u) = s(ls(u)) + s(rs(u));}
inline void clear(int u,int l,int r){
tag(u) = -1;s(u) = 0;
clear(ls(u),l,mid);clear(rs(u),mid + 1,r);
}
inline void cover(int u,int l,int r,int lt,int rt,int p){
// std::cout<<"COVER "<<u<<" "<<l<<" "<<r<<" "<<lt<<" "<<rt<<" "<<p<<"\n";
if(lt <= l && r <= rt){tag(u) = p;s(u) = (r - l + 1) * tag(u);return ;}
push(u,l,r);
if(lt <= mid)cover(ls(u),l,mid,lt,rt,p);
if(rt > mid)cover(rs(u),mid + 1,r,lt,rt,p);
up(u);
}
inline int query(int u,int l,int r,int lt,int rt){
int ans = 0;
if(lt <= l && r <= rt){return s(u);}
push(u,l,r);
if(lt <= mid)ans += query(ls(u),l,mid,lt,rt);
if(rt > mid)ans += query(rs(u),mid + 1,r,lt,rt);
return ans;
}
struct P{int l,r,v;}A[N],p[N];
using std::map;
map<int,int>L,R;
bool operator < (P A,P B){return A.v > B.v;}
inline bool check(int lim){
for(int i = 1;i <= lim;++i)p[i] = A[i];
cover(root,1,n,0);
L.clear();R.clear();
std::sort(p + 1,p + lim + 1);
// std::cout<<lim<<"\n";
// for(int i = 1;i <= lim;++i)std::cout<<p[i].l<<" "<<p[i].r<<" "<<p[i].v<<"\n";
int las = 1;
for(int i = 1;i <= lim;++i){
//judge and
if(p[i].v != p[i - 1].v){
while(p[las].v != p[i].v){
cover(root,p[las].l,p[las].r,1);
++ las;
}
}
if(L[p[i].v] == 0){L[p[i].v] = p[i].l;R[p[i].v] = p[i].r;}
else {L[p[i].v] = std::max(L[p[i].v],p[i].l);R[p[i].v] = std::min(R[p[i].v],p[i].r);}
if(R[p[i].v] < L[p[i].v]){return 0;}
//judge cover
// std::cout<<"QUERY "<<L[p[i].v]<<" "<<R[p[i].v]<<"\n"<<query(root,L[p[i].v],R[p[i].v])<<"\n";
if(query(root,L[p[i].v],R[p[i].v]) == (R[p[i].v] - L[p[i].v] + 1))return 0;
}
return 1;
}
int main(){
scanf("%d%d",&n,&q);
for(int i = 1;i <= q;++i){scanf("%d%d%d",&A[i].l,&A[i].r,&A[i].v);}
int l = 1,r = q;
while(l + 1 < r){
// std::cout<<l<<" "<<r<<" "<<check(mid)<<"\n";
if(check(mid)){l = mid;}
else r = mid - 1;
}
int ans;
if(check(r))ans = r;
else if(check(l))ans = l;
if(check(q))ans = q;
if(ans == q)puts("0");else std::cout<<ans + 1<<"\n";
}
/*
20 4
1 10 7
5 19 7
3 12 8
11 15 12
*/
[x] k-Maximum Subsequence Sum
每次单点改
询问一个区间上 \(k\) 个非交子序列的最大和.
赛场上完全没懂,下来一看原来以前做过这个题,果然以前训练也太抽象了。
考虑使用反悔贪心的操作,每次选取一个区间最大子段和,然后把这一段取反,再取最大子段和,重复 \(k\) 次即可.
然后代码不是很好写,但也还行,就是比较麻烦,中间被没预设取反标志值坑了很久.
//山桃红花满上头,蜀江春水拍山流。
#include<bits/stdc++.h>
#define N 100005
struct seg{
int l,r,s;
seg(int l = 0,int r = 0,int s = 0):l(l),r(r),s(s){}
};
seg operator + (seg A,seg B){return seg(A.l,B.r,A.s + B.s);}
bool operator < (seg A,seg B){return (A.s < B.s);}
struct node{
seg lmi,lmx;
seg rmi,rmx;
seg mi,mx;
seg all;
bool rev;
}T[N << 2];
#define lmi(x) x.lmi
#define lmx(x) x.lmx
#define rmi(x) x.rmi
#define rmx(x) x.rmx
#define mx(x) x.mx
#define mi(x) x.mi
#define all(x) x.all
#define tag(x) x.rev
#define L(x) x.l
#define R(x) x.r
#define S(x) x.s
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
inline void init(node &x,int t,int p){
L(lmi(x)) = L(lmx(x)) = L(rmi(x)) = L(rmx(x)) = L(all(x)) = L(mi(x)) = L(mx(x)) = t;
R(lmi(x)) = R(lmx(x)) = R(rmi(x)) = R(rmx(x)) = R(all(x)) = R(mi(x)) = R(mx(x)) = t;
S(lmi(x)) = S(lmx(x)) = S(rmi(x)) = S(rmx(x)) = S(all(x)) = S(mi(x)) = S(mx(x)) = p;
tag(x) = 0;
}
node operator + (node A,node B){
node x;
lmi(x) = std::min(lmi(A),all(A) + lmi(B));
lmx(x) = std::max(lmx(A),all(A) + lmx(B));
rmi(x) = std::min(rmi(B),rmi(A) + all(B));
rmx(x) = std::max(rmx(B),rmx(A) + all(B));
mi(x) = std::min(rmi(A) + lmi(B),std::min(mi(A),mi(B)));
mx(x) = std::max(rmx(A) + lmx(B),std::max(mx(A),mx(B)));
all(x) = all(A) + all(B);
tag(x) = 0;
return x;
}
inline void prind(seg U){printf("[%d,%d],sum: %d\n",L(U),R(U),S(U));}
inline void print(node U,int l,int r){
puts("----------------------------");
printf("THE BLOCK OVER WITH [%d,%d] : \n",l,r);
printf("THE LMI:");prind(lmi(U));
printf("THE LMX:");prind(lmx(U));
printf("THE RMI:");prind(rmi(U));
printf("THE RMX:");prind(rmx(U));
printf("THE MI:");prind(mi(U));
printf("THE MX:");prind(mx(U));
printf("THE ALL:");prind(all(U));
puts("----------------------------");
}
inline void flip(node &x){
tag(x) ^= 1;
std::swap(lmi(x),lmx(x));std::swap(rmi(x),rmx(x));std::swap(mi(x),mx(x));
S(lmi(x)) *= -1;S(lmx(x)) *= -1;S(rmi(x)) *= -1;S(rmx(x)) *= -1;
S(mi(x)) *= -1;S(mx(x)) *= -1;
S(all(x)) *= -1;
}
inline void push(int u){
if(tag(T[u])){
flip(T[ls(u)]);
flip(T[rs(u)]);
tag(T[u]) = 0;
}
}
int n;
int a[N];
#define root 1,1,n
#define mid ((l + r) >> 1)
inline void build(int u,int l,int r){
if(l == r){init(T[u],l,a[l]);return ;}
build(ls(u),l,mid);build(rs(u),mid + 1,r);
T[u] = T[ls(u)] + T[rs(u)];
tag(T[u]) = 0;
}
inline void change(int u,int l,int r,int t,int p){
if(l == r){init(T[u],t,p);return ;}
push(u);
if(t <= mid)change(ls(u),l,mid,t,p);
if(t > mid)change(rs(u),mid + 1,r,t,p);
T[u] = T[ls(u)] + T[rs(u)];
}
inline void cover(int u,int l,int r,int tl,int tr){
if(tl <= l && r <= tr){
flip(T[u]);
return ;
}
push(u);
if(tl <= mid)cover(ls(u),l,mid,tl,tr);
if(tr > mid)cover(rs(u),mid + 1,r,tl,tr);
T[u] = T[ls(u)] + T[rs(u)];
}
inline node query(int u,int l,int r,int tl,int tr){
node ansA,ansB;
L(lmi(ansA)) = L(lmi(ansB)) = -1;
if(tl <= l && r <= tr)return T[u];
if(tl <= mid)ansA = query(ls(u),l,mid,tl,tr);
if(tr > mid)ansB = query(rs(u),mid + 1,r,tl,tr);
if(L(lmi(ansA)) == -1)return ansB;
if(L(lmi(ansB)) == -1)return ansA;
return ansA + ansB;
}
int q;
using std::queue;
std::queue<seg>U;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%d",&a[i]);
build(root);
scanf("%d",&q);
while(q --){
int op;
scanf("%d",&op);
if(op == 0){int t,x;scanf("%d%d",&t,&x);change(root,t,x);}
if(op == 1){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int ans = 0;
for(int i = 1;i <= k;++i){
node segment = query(root,l,r);
if(S(mx(segment)) > 0){
ans += S(mx(segment));
cover(root,L(mx(segment)),R(mx(segment)));
U.push(mx(segment));
}
}
std::cout<<ans<<"\n";
while(U.size()){
seg top = U.front();
U.pop();
cover(root,L(top),R(top));
}
}
}
}
2024 (ICPC) Jiangxi Provincial Contest
24-7-5 12:00 - 17:00
下面指的是我和队友一起做的情况
[√]A
a + b + c
[√]C
发现如果加起来刚好等于 \(S\),则答案为 \(n\),否则答案为 \(n - 1\)
[√]D
gcd 是质因数次数取 min
lcm 是质因数次数取 max
死去的离散数学整除格正在攻击我
考虑实际上是把每一维质因数的次数排序。
因为 \(x + y \leq gcd(x,y) + lcm(x,y)\),所以最后每一维的应该都是排序好的。
质因数分解然后硬来就行了。
[√]G
队友写的,这里看看题解做法。
考虑每一位的取模贡献 \(11^x * a_x \mod 5\)
又因为 \(11 ^ x \mod 5 = 1\)
所以就是加起来取模 \(5\) 即可
[√]H
什么机器学习题
考虑卷积核上每一位的贡献在原矩阵上就是一个矩阵和,使用二维前缀和算出每一位贡献即可。
[√]J
麻将模拟题。
原本以为要判很多牌型,结果只有国士无双十三面和七对子。
队友看懂题之后就直接过了。
[√]K
考虑实际上等价于 \((1,1) \to (2,n)\) 的方案数,实际上的答案就是 \(2 ^ (n - 1)\)
[√]L
考虑 \(K\) 非常小,实际上只要每次在一个门开门的时候跑一遍单源最短路,在关门的时候把答案去掉即可。
[√]F
队友上来就toptree直接写了。
非常的牛。
我只会那个线段树分治加并查集的 2log 做法,但是鉴于我队友看起来是数据结构机器人,感觉很难有他不会的数据结构题,我就不写了。
[√]I
考虑到覆盖边其实是无所谓的条件。
转化成覆盖点。
然后考虑枚举所有的外接圆,一个圆可以由 \(3\) 个在圆上的点确定,或者 \(2\) 个直径上的点确定,直接 \(O(n^2 + n^3)\) 枚举即可。
然后考虑一个圆内能覆盖多少个点,然后统计答案即可。
[x]E
给定一个序列,求找出两个不同的子序列,要求两个子序列和相同,给出方案。
非常苦手,队友上来说应该是什么随机题,非常神秘。
首先知道一个长度 \(n\) 的序列,其子序列数为 \(2^n\)
其值域 \(S \in [0,A * n],v_i \leq A\)
那在 \(29 \leq n\) 时 存在 \(A * n \leq 2 ^ n\)
这说明如果一个序列长度大于等于 \(29\) 时,其一定存在两个不同子序列的和相同(鸽笼原理)
那么我们实际上只需要处理 \(30\) 长度的序列,如果长度大于三十,我们取前三十个元素即可。
接下来我们考虑如何处理 \(30\) 长度的序列了,我们考虑强制最终的答案不交,那么一个元素的处理方法就是丢到字序列A,丢到子序列B,和完全不动。
我们可以使用 mid in mid 的做法,我们处理前十五个的情况和后十五个的情况
容易想到的是我们直接就地使用一个 map,然后每个 map 的下标为 子序列A的和 与 子序列B的和 的差值,然后存的是对应一个状压的三进制数,其中每一位0/1/2表示这个元素没动/在A/在B。
那么我们在 mid in mid 最后统计结果,就是前十五个的 A - B 的差值是后十五个某方案的 A - B 差值的相反数,然后统计方案即可。
但是这样的复杂度大抵是 \(O(3^{\min(15,\frac{n}{2})}\log(3^{\min(15,\frac{n}{2})}))\)
有点通过不了。
这里其实有很多种处理方法,后面会提到,先介绍题目所说的三个序列归并的方法。
我们考虑我们在搜索状压的时候,做的其实是二元状态 \((s,d)\) 的变更
我们从前 \(i - 1\) 个元素的方案加入第 \(i\) 个元素,然后考虑更新方案
实际上是
\((s,d) \to (s \times 3,d)\)
\((s,d) \to (s \times 3 + 1,d + a_i)\)
\((s,d) \to (s \times 3 + 2,d - a_i)\)
分别对应上述三种加入方案
那么我们只要在加入到第\(i-1\)元素,就把所有的 \(3^{i-1}\) 的二元状态按照 \(d\) 排好序,那么我们加入第 \(i\) 个元素的时候,实际上是对上面三种序列归并排序即可。
你可以直接把三个序列先分别求出来再排序,也可以直接维护三个头指针,三指针移动的把三种对应的最小的先放进 \(3^i\) 个状态对应的答案序列里,然后加对应指针向后挪一位即可。
然后我们得到了前十五个的状态,和后十五的状态,由于均有序,我们直接就双指针查有没有相反数即可。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 1000000
#define M 43046721
int T;
int a[N];
int n;
struct P{
int s_val;int dval;
P(int sv = 0,int dv = 0):s_val(),dval(){};
};
bool operator < (P A,P B){return A.dval < B.dval;}
bool operator == (P A,P B){return A.s_val == B.s_val && A.dval == B.dval;}
void init(P &A,int sv,int dv){A.s_val = sv;A.dval = dv;}
using std::vector;
vector<P>S[2];
/*
0 contain A - B
1 contain B - A
*/
#define inf 5000000000
inline void print(int len,int ind){
for(auto [s_val,dval] : S[ind]){
for(int i = 0;i < len;++i){std::cout<<s_val % 3<<" ";s_val /= 3;}
std::cout<<"val :"<<dval<<"\n";
}
}
inline void del(int l,int r,int ind){//del with [l,r]
int len = r - l + 1;
S[ind].clear();
S[ind].emplace_back(0,0);
for(int i = l;i <= r;++i){
int tnull = 0,tA = 0,tB = 0;
vector<P>ans;
while(tnull < S[ind].size() || tA < S[ind].size() || tB < S[ind].size()){
P cnull,cA,cB;
if(tnull < S[ind].size())init(cnull,S[ind][tnull].s_val * 3,S[ind][tnull].dval);else init(cnull,0,inf);
if(tA < S[ind].size())init(cA,S[ind][tA].s_val * 3 + 1,S[ind][tA].dval + (ind == 0 ? 1 : -1) * a[i]);else init(cA,0,inf);
if(tB < S[ind].size())init(cB,S[ind][tB].s_val * 3 + 2,S[ind][tB].dval + (ind == 0 ? -1 : 1) * a[i]);else init(cB,0,inf);
P minn = std::min(cnull,std::min(cA,cB));
if(cnull == minn){ans.push_back(cnull);tnull ++ ;}
if(cA == minn){ans.push_back(cA);tA ++ ;}
if(cB == minn){ans.push_back(cB);tB ++ ;}
}
S[ind] = ans;
}
// print(len,ind);
}
vector<int>ans[3];
inline void find(int l,int r,int St){
for(int i = r;i >= l;--i){
ans[St % 3].push_back(i);
St /= 3;
}
}
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i){scanf("%d",&a[i]);}
if(n == 1){puts("-1");continue;}
// puts("Yes");
del(1,std::min(15,n / 2),0);del(std::min(16,n / 2 + 1),std::min(30,n),1);
int tpre = 0,tend = 0;
bool flg = 0;
ans[0].clear();ans[1].clear(),ans[2].clear();
while(tpre < S[0].size() && tend < S[1].size()){
if(S[0][tpre].dval == S[1][tend].dval && S[0][tpre].s_val != 0){
flg = 1;
find(1,std::min(15,n / 2),S[0][tpre].s_val);
find(std::min(16,n / 2 + 1),std::min(30,n),S[1][tend].s_val);
break;
}
if(S[0][tpre] == std::min(S[0][tpre],S[1][tend]))tpre ++ ;else tend++;
}
if(!flg)puts("-1");
if(flg){
std::cout<<ans[1].size()<<" ";
for(auto v : ans[1])std::cout<<v<<" ";
puts("");
std::cout<<ans[2].size()<<" ";
for(auto v : ans[2])std::cout<<v<<" ";
puts("");
}
}
}
[√]忘情
求给定一序列,将其分为 \(m\) 段,每段代价为 \(\frac{(\sum_{i = 1}^{n}x_i \times \overline{x})}{\overline{x}^2}\),求最小代价
容易推导到代价实际为 \((1 + \sum x_i)^2\)
如果没有 \(m\) 段的代价实际上,我们可以直接使用斜率优化完成这个题
\(j \to i : f_i = f_j + (S_i - S_j + 1) ^ 2 \to f_i = f_j + S_i^2 - 2\times S_i\times (S_j - 1) + (S_j - 1) ^ 2\)
\(2\times S_i \times (S_j - 1) + f_i - S_i ^ 2 = f_j + (S_j - 1)^2\)
即存在一条斜率为 \(2\times S_i\) 的直线通过 \((S_j - 1,f_j + (S_j - 1)^2)\)
由于要 \(f_i\) 最小,则是求最小截距,那即为维护下凸包单调队列即可
接着考虑如何强制选 \(m\) 段
这是一个经典的条件,我们考虑 \(g_k\) 表示分了 \(k\) 段的答案,其显然是一个上凸包
我们考虑二分凸包斜率,找到 \(m\) 所在的凸包位置即可。
然后这个斜率我们以 \((k,lk)\) 的形式表示,其实即为每次转移附带了一个 \(l\) 的代价
然后我们返回可以分的段数
这里需要注意可能同一斜率上有多个点,这里应该返回最小的段数/最大的段数,后面二分自洽即可,最后的答案减去 \(m\times l\)
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 100005
int n,m;
int a[N];
ll pre[N];
int minn = 2000;
ll f[N];
int t[N];
struct P{
ll x;ll y;int time;
P(ll x_val = 0,ll y_val = 0,int t = 0):x(x_val),y(y_val),time(t){};
};
P s[N];
int top,end;
inline void print(P A){std::cout<<"( "<<A.x<<" "<<A.y<<" "<<A.time<<")"<<"\n";}
inline int check(ll d){
top = end = 1;
s[end] = P(-1,1 + d);
for(int i = 1;i <= n;++i)f[i] = 0;
for(int i = 1;i <= n;++i){
while((end > top) && (2 * pre[i] * (s[top + 1].x - s[top].x) > (s[top + 1].y - s[top].y)))++top;
f[i] = s[top].y - 2 * pre[i] * s[top].x + pre[i] * pre[i];
t[i] = s[top].time + 1;
P now(pre[i] - 1,(pre[i] - 1) * (pre[i] - 1) + d + f[i],t[i]);
while(end > top && (now.y - s[end].y) * (s[end].x - s[end - 1].x) < (s[end].y - s[end - 1].y) * (now.x - s[end].x))--end;
s[++end] = now;
}
return t[n];
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i){scanf("%d",&a[i]);pre[i] = pre[i - 1] + a[i];minn = std::min(a[i],minn);}
ll l = 0,r = 1e18;
#define mid ((l + r) >> 1)
while(l + 1 < r){if(check(mid) > m)l = mid;else r = mid;}
ll ansd = 0;
if(check(l) <= m)ansd = l;
else if(check(r) <= m)ansd = r;
int used = check(ansd);
std::cout<<f[n] - 1ll * m * ansd<<"\n";
}
[√]CF1499F Diameter Cuts
给定一颗 \(n\) 个节点的树和一个正整数 \(k\),求多少种删边集合,让删边后的所有连通块直径不大于 \(k\)
令 \(f_{i,j}\) 为 \(i\) 到父亲的边不断,其父亲到 \(i\) 子树内最长路径为 \(j\) 的方案数
特殊的 \(f_{i,0}\) 表示断开 \(i\) 和父亲的边
考虑如何统计答案,实际上就是任两子树内最长路径相加不大于 \(k\) 的方案数
考虑如果显然子树内不能存在两条大于 \(\frac{k}{2}\) 的链,先对所有子树前缀统计,然后枚举大于 \(\frac{k}{2}\) 的在哪个子树,然后其余的所有子树限制为 \(k - l\),\(l\) 为枚举的最长链。
若不存在大于 \(\frac{k}{2}\) 正常前缀算答案即可。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 5005
#define mod 998244353
int f[N][N];// n longest n
int pre[N][N];// sum of pre
int fa[N];
int ps[N];
using std::vector;
vector<int>G[N];
int n,k;
inline int qpow(int x,int t){
int a = x;
int ans = 1;
while(t){
if(t & 1)ans = 1ll * ans * a % mod;
a = (1ll * a * a) % mod;
t >>= 1;
}
return ans;
}
inline int inv(int x){return qpow(x,mod - 2);}
inline void dfs(int u = 1,int ffa = 0){
fa[u] = ffa;
for(auto v : G[u]){
if(v == fa[u])continue;
dfs(v,u);
}
}
inline void del(int u = 1){
for(int i = 0;i <= k;++i)ps[i] = 1;
for(auto v : G[u]){
if(v == fa[u])continue;
del(v);
}
for(int i = 0;i <= k;++i)ps[i] = 1;
for(auto v : G[u]){
if(v == fa[u])continue;
for(int i = 0;i <= k;++i)ps[i] = 1ll * ps[i] * pre[v][i] % mod;
}
/*connected*/
for(int i = 0;i <= k / 2;++i)f[u][i + 1] = (ps[i] - (i != 0 ? ps[i - 1] : 0) + mod) % mod;
/*unconneted*/
f[u][0] = 0;
for(auto v : G[u]){
if(v == fa[u])continue;
for(int i = k / 2 + 1;i <= k;++i){
int now = 1ll * f[v][i] * ps[k - i] % mod * inv(pre[v][k - i]) % mod;
f[u][0] = (1ll * f[u][0] + now) % mod;
f[u][i + 1] = (1ll * f[u][i + 1] + now) % mod;
}
}
f[u][0] = (f[u][0] + ps[k / 2]) % mod;
pre[u][0] = f[u][0];
for(int i = 1;i <= k + 1;++i)pre[u][i] = (pre[u][i - 1] + f[u][i]) % mod;
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs();
del();
std::cout<<f[1][0]<<"\n";
}
Codeforces Round #956 (Div. 2) and ByteRace 2024
[√]A
构造一个 \((\sum_{i | k}a_i) | k\) 的数列
直接输出 \(1,...,n\) 即可
[√]B
给定两矩阵 \(A,B\),每次可以选择一个矩形的四个角,左上角和右下角加 \(1\),右上角左下角加 \(2\),所有元素对 3 取模
考虑到实际上每行每列的对 \(3\) 取模不变,容易证明其为充要条件。
充分条件可以考虑每行调整即可。
[√]C
给定 \(n\) 元序列 \(A,B,C\), 每个序列和均为 \(sum\) , 求把 \(A,B,C\) 取三个下标不交的子段,要求每个子段和大于 \(\frac{1}{3} sum\)
考虑枚举中间那段在哪,然后双指针即可,考虑显然是前一段为 \([1,l - 1]\),后一段为 \([r + 1,n]\)
判断是否满足条件即可
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 1000005
int n;
int a[N][3];
ll prea[N][3];
ll enda[N][3];
ll tot;
int T;
int ans[2][3];
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int j = 0;j <= 2;++j)
for(int i = 1;i <= n;++i)scanf("%d",&a[i][j]);
for(int j = 0;j <= 2;++j)
for(int i = 0;i <= n + 1;++i)prea[i][j] = enda[i][j] = 0;
for(int j = 0;j <= 2;++j)
for(int i = 1;i <= n;++i)prea[i][j] = prea[i - 1][j] + a[i][j];
for(int j = 0;j <= 2;++j)
for(int i = n;i >= 1;--i)enda[i][j] = enda[i + 1][j] + a[i][j];
bool flg = 0;
ll target = std::ceil(1.0 * prea[n][0] / 3);
// std::cout<<target<<"\n";
for(int j = 0;j <= 2;++j){
// std::cout<<"check "<<j<<"\n";
int t1 = (j + 1) % 3,t2 = (j + 2) % 3;
int ta = 1;
for(int i = 2;i <= n;++i){
while(prea[i][j] - prea[ta][j] >= target)ta ++ ;
// std::cout<<ta<<" "<<i<<"\n";
// std::cout<<t1<<" "<<prea[ta - 1][t1]<<" "<<t2<<" "<<enda[i + 1][t2]<<"\n";
if(prea[ta - 1][t1] >= target && enda[i + 1][t2] >= target){
ans[0][t1] = 1,ans[1][t1] = ta - 1;
ans[0][j] = ta,ans[1][j] = i;
ans[0][t2] = i + 1,ans[1][t2] = n;
flg = 1;
break;
}
std::swap(t1,t2);
// std::cout<<t1<<" "<<prea[ta - 1][t1]<<" "<<t2<<" "<<enda[i + 1][t2]<<"\n";
if(prea[ta - 1][t1] >= target && enda[i + 1][t2] >= target){
ans[0][t1] = 1,ans[1][t1] = ta - 1;
ans[0][j] = ta,ans[1][j] = i;
ans[0][t2] = i + 1,ans[1][t2] = n;
flg = 1;
break;
}
}
if(flg)break;
}
if(flg){
for(int i = 0;i <= 2;++i)
for(int j = 0;j <= 1;++j)std::cout<<ans[j][i]<<" ";
puts("");
}else puts("-1");
}
}
[√]D
给定两个序列 \(A,B\),每次可以在 \(A\) 和 \(B\) 中选择两个下标差相同的\((A_i,A_j),(B_p,B_q)\),将 \(A_i,A_j\) 值交换,\(B_p,B_q\) 交换
问能否最后相同。
显然权值集合要相同
然后跨越 \(d\) 的交换可以拆分成若干相邻交换,相邻交换逆序对奇偶改变
所以充要条件是 \(A,B\) 的逆序对奇偶相同
考虑充分条件构造,直接把一个调整到 \(1,...,n\),然后调整另外一边,此时另外一遍调整一定是偶数次,在调整好的一直选中相邻两个交换即可。
[√]F
给定一个序列 \(A\),其中一个区间的代价为 $val_{l,r} = \min_{l\leq x,y\leq r} A_X \oplus A_y $
问区间代价第 \(k\) 个为多少。
先二分
然后考虑如何计算 \(\leq mid\) 的答案
我们考虑枚举子点对的右端点 \(r\),我们找到离他最近的点 \(l\), 满足 \(A_r \oplus A_l \leq mid\),那么包含其的区间数量为 \([1,l]\times[r,n]\)
考虑如何统计所有的数量,你显然可以 \(2log\) 来矩阵并,这样你就爆爆了
我们考虑可以扫描线,我们双指针维护前缀最大的 \(l\),这个点的最近的点对为 \(l'\),那么新增的答案为 \(\max(l' - l,0)\times (n - r + 1)\)
只有 \(l' > l\) 有贡献,那么我们直接维护前缀指针 \(l\) ,每次尝试暴力向右移动即可,判断 \([l,r]\) 中是否右\(A_i \oplus A_r \leq mid\)
使用 01-tire 来进行查询过程,两支 \(log\)
也有一个 \(log\) 的做法,直接每位判定,但是主播比较懒,这个就不写了,当时比赛打一半去看 石油杯了。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 100005
#define inf 2000000000
#define int ll
using std::multiset;
int T;
int n,k;
int a[N];
int A[N * 50][2];
int siz[N * 50];
int cnt = 1;
inline void insert(int x){
int rt = 1;
for(int i = 31;i >= 0;--i){
siz[rt] ++ ;
int t = ((x >> i) & 1);
if(!A[rt][t])A[rt][t] = ++cnt;
rt = A[rt][t];
}
siz[rt] ++ ;
}
inline void erase(int x){
int rt = 1;
if(x == -1)return ;
for(int i = 31;i >= 0;--i){
siz[rt] -- ;
int t = ((x >> i) & 1);
if(!A[rt][t])A[rt][t] = ++cnt;
rt = A[rt][t];
}
siz[rt] -- ;
}
inline int find(int x){
int rt = 1;
int ans = 0;
if(siz[rt] == 0)return inf;
for(int i = 31;i >= 0;--i){
int t = ((x >> i) & 1);
if(siz[A[rt][t]])rt = A[rt][t];
else rt = A[rt][t ^ 1],ans |= (1ll << i);
}
return ans;
}
int lasl[N];
inline int check(int x){
int l = 0;
int ans = 0;
a[0] = -1;
for(int i = 1;i <= n;++i){
bool flg = (find(a[i]) <= x);
if(flg){
while(find(a[i]) <= x){erase(a[l ++ ]);}
insert(a[-- l]);
ans = ans + (l - lasl[i - 1]) * (n - i + 1);
}
lasl[i] = l;
insert(a[i]);
}
while(l <= n){erase(a[l ++ ]);}
return ans;
}
inline void slove(){
int l = 0,r = inf;
#define mid ((l + r) >> 1)
while(l + 1 < r){
if(check(mid) >= k)r = mid;
else l = mid + 1;
}
if(check(l) >= k)std::cout<<l<<"\n";
else std::cout<<r<<"\n";
}
signed main(){
scanf("%lld",&T);
while(T -- ){
scanf("%lld%lld",&n,&k);
for(int i = 1;i <= n;++i)scanf("%lld",&a[i]);
slove();
}
}
[x]E
我们只先计算先手球的期望。
首先思考到所有的特殊球和普通球的贡献是分开的
然后普通球的贡献是好计算的,$\lfloor\frac{c + 1}{2} \rfloor\frac{\sum_{i \in C} a_i}{c} $
然后思考特殊球的贡献,我当时以为是分成两段,但是后来看来是用 \(c\) 个普通球,分成了 \(c + 1\) 段,每人按顺序取,所以特殊球的期望为 \(\lfloor\frac{c + 2}{2}\rfloor \frac{\sum_{i \in S}a_i}{c + 1}\)
The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest
[√]A
考虑根号操作只有 \(\sqrt x\) 个数,若平方完再根号,对于不同的数没有任何贡献。
那么考虑先对 \(a\) 取下取整根号操作得到 \(b\),若 \(b^2 = a\) 则无贡献,否则有剩余贡献即可。
[√]D
队友发现答案一直是lose,输出 \(n\) 个 lose 即可。
[√]E
要求 \(B = \text{popcount}(A) + \text{popcount}(B)\mod 2^k\)
考虑反过来 \(B\) 固定时,\(\text{popcount}(A)\) 唯一即可
[√]M
考虑如何不重复的计这个,考虑枚举中间按两个点,然后尝试枚举下面矩形点对,然后正常统计即可。队友写了各种叉积判断避免了精度问题。
[√]H
给定 \(n\) 个点的树,\(m\) 个点对,可选择一个点为根,要求所有 \(2m\) 个点到它们对应的 \(LCA\) 最大距离最小。
考虑二分答案,对于一个点对的方案形为 \(k\) 极祖先的子树内/外。
考虑其为 \(\text{dfs}\) 序的 O(1) 段区间,查询是否交集为空即可。
[√]I
考虑最后一个完整排列的 \(f_i\)
\(f_i = \sum_{j = 1}^k f_{i - j} \times g_j\)
其中 \(g_j\) 为前缀 \(j\) 均不为子排列的方案数。
这个考虑容斥即可
\(g_i = i! - \sum_{j = 1}^{i - 1} j! \times g_{i - j}\)
[√]K
考虑一层层的从右到左的放置 \(r_i\)
下一层查询在上一层的左端点和其离的最近的 \(r_i\),判断若 \(l = l'\),则需要 \(r = r' - 1\) 否则 \(r = r'\)
[√]L
考虑先取所有最小单位 \(()\) ,然后依次打括号即可
[√]F
若 \(\frac{p}{q}\) 为有限小数,统计 \(p\) 。
对 \(q\) 的质因子 \(p_i\) ,其次幂小于等于 \(p\) 的幂次
考虑直接 \(\text{dfs}\) 即可。
[√]G
考虑对出现次数根号分治即可
含有出现次数大的那边离线下来做,小的直接暴力。
[√] B
实际上每一层都实际上依赖于第二层
假设二级一开始是工作状态,则一级一开始是不工作状态,三级一开始是辅助供能状态。
注意到三级的供能站想要转为供能状态,则需要二级处于不工作状 态;一级供能站想要转为供能状态,则需要二级处于辅助供能状态。
考虑将第二层拆成 供能转不工作 不工作转辅助
然后将有需求的向 供能转不工作 连边
一级功能向两者同时连,均为无穷,最小割割不掉即可。
复杂度正确性存疑。
[×] C
思考到:
你第一次改成了什么,最后就是什么
两个方向的答案是一样的
那么考虑如何快速算答案,双指针预处理出 \(f_{i,{0/1}}\) 为区间 \([i,(i + k - 1) \mod n]\) 变成\(0/1\) 的下一步操作该到哪里。
然后枚举第一步在哪,然后倍增即可。
代码暂无
Codeforces Round 958 (Div. 2)
[√]A
显然每次都扩展 \(k - 1\) 个 1
[√]B
考虑可以先把所有的长度大于 \(1\) 的 \(0\) 串先变成长度为 \(1\) 的。
接着检查是否 \(1\) 为剩下串的众数即可。
[√]C
考虑到要求相邻 \(\text{or}\) 的结果为 \(n\),那么即所有数的二进制下 \(1\) 集合为 \(n\) 的子集。
又要求严格递增,那么考虑依次从低位去掉 \(1\) 即可。
[√] D
又是一场想完没写完的做法。
场上一直写的是线性做法,我说怎么大家过的都这么快。
首先题意要求每次删除若干不连通点,最后求每个回合中的点权和。
考虑我们在第 \(i\) 回合删除了点 \(x\),他对最后答案的贡献为 \(i \times a_x\) 。
那么我们将题目转化为我们需要为每个点赋上一个权 \(b_i\),要求相邻点的赋权不相同,最小化 \(\sum b_i \times a_i\)
我们容易写出 \(f_{i,j}\) 表示 \(i\) 点赋权 \(j\),子树内 \(\sum b_x \times a_x\) 的最小和。
容易写出转移式即 \(f_{i,j} = \sum_{v \in son_i} \min_{k \neq j} f_{v,k} + j \times a_i\)
我们知道 \(b_i \leq n\) ,但是 \(n\) 只是我们随手估的一个不太好的上界,在官方题解中给出了一个更好的上界 \(b_i \leq \log n\),于是可以在 \(O(n\log^2 n)\) 或者 \(O(n\log n)\) 中解决这个问题。
但是我们有完全不需要基于上界的做法。
我们思考 \(\min_{k\neq j} f_{v,k}\) 这个函数,实际上的值是一个分类函数。
我们设 \(s\),\(f_{v,s} = \min f_{v,k}\)。
同时设 \(s'\),\(f_{v,s'} = \min_{k \neq s}f_{v,s'}\)。
即 \(v\) 的最小值和次小值点。
那么 \(\begin{equation} \min_{k \neq j} f_{v,k}=\left\{ \begin{aligned} f_{v,s} \quad j \neq s\\ f_{v,s'} \quad j = s\\ \end{aligned} \right . \end{equation}\)
于是我们发现实际上 \(\sum_{v \in son_i} \min_{k \neq j} f_{v,k}\) 只有至多 \(|son_i| + 1\) 种取值,即我们取任一子树的最小值对应的点,和不取任何一个所对应的值,这里我们称儿子的最小值的点的集合为 \(S\) 。
那么我们发现我们实际上并不需要维护所有点,我们只需要维护一个点的最小值和次小值位置即可。
那么我们接着来考虑后面这个 \(j \times a_i\) 函数,其为一个递增函数。我们考虑我们取任一子树的最小值对应的点,\(j\) 是固定无法修改的。但是对 \(j \not \in S\) ,我们只需要取 \(\text{mex}(S)\) 和 \(\text{mex}(S + \text{mex}(S))\) 。
在 \(|son_i| + 2\) 种答案中就能取到最小值和次小值。
这里我们采取了直接暴力一点的做法我直接取了 \(S,S + 1,S + 2\) 三种情况,在 \(3|son_i|\) 种情况取最小值和次小值,容易证明 \(\text{mex}(S) + \text{mex}(S + \text{mex}(S)) \in \{S + 1\} \cup \{S + 2\}\) 。
同时我在代码里使用 \(\text{map}\),可以使用 数组 或 \(\text{Hash}\) 表等其他线性映射结构来替代掉这个 \(\text{log}\) 。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 5000005
int T;
ll a[N];
using std::vector;
vector<int>G[N];
int n;
struct P{int ind;ll val;}minn[N],minm[N];
bool operator < (P A,P B){return A.val < B.val;}
bool operator <= (P A,P B){return A.val <= B.val;}
using std::map;
map<int,ll>dval;
vector<int>check;
inline void dfs(int u,int fa){
if(G[u].size() == 0 || (G[u].size() == 1 && G[u][0] == fa)){minn[u].ind = 1;minn[u].val = a[u];minm[u].ind = 2;minm[u].val = 2 * a[u];return ;}
for(auto v : G[u]){
if(v == fa)continue;
dfs(v,u);
}
dval.clear();check.clear();
ll allval = 0;
check.push_back(0);
for(auto v : G[u]){
if(v == fa)continue;
check.push_back(minn[v].ind);
allval += minn[v].val;
dval[minn[v].ind] += minm[v].val - minn[v].val;
}
P nowminn,nowminm;
nowminn.ind = nowminm.ind = 0;
nowminn.val = nowminm.val = 4e18;
for(int i = 1;i < check.size();++i){
ll x = check[i];
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
for(int i = 0;i < check.size();++i){
ll x = check[i] + 1;
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn ){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
for(int i = 0;i < check.size();++i){
ll x = check[i] + 2;
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn ){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
minn[u] = nowminn,minm[u] = nowminm;
}
signed main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%lld",&a[i]);
for(int i = 1;i <= n;++i)G[i].clear();
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,0);
std::cout<<minn[1].val<<"\n";
}
}
Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)
[√]A
令每个元素为其加一即可。
[√]B
如果前缀有一个地方是 \(1\) 即可.
for(int i = 1;i <= n;++i){
if(s[i] == 1)break;
if(s[i] != t[i]){flg = 0;break;}
}
flg ? puts("YES") : puts("NO");
[√]C
对每个点维护从这个点 \(0\) 有多少答案。
然后直接双指针,维护到后缀哪里变 \(0\) 即可。
[√]D
很有意思的一题,大胆猜测最后一定能连成一颗树
这里给定一个证明,考虑轮数从大到小
令 \(A\) 为全点集,令 \(S\) 为已经加入到生成树中的点,则我们假设 \([k,n]\) 轮都能满足新加一个点进 \(S\)
则在 \(k - 1\) 轮,我们考虑 \(|S| = n - k + 1,|A - S| = k - 1\)
则依据鸽巢原理定 \(x \in A - S,y \in S,x \mod k = y\mod k\)
那么知道这个那直接连边即可。
[√]E
很好的诈骗题,让我头脑旋转
考虑每颗树可以得到 \([0,s]\) 的任意树,实际上就是所有的值 \(or\) 起来最大即可。
[x]F
回忆欧拉回路的条件,每个点的度数都为偶数。
考虑转成奇偶,那么实际上就是给定了一个初态 \(S\),判断若干 \((x,y)\),每次操作形如取反 \(S_x,S_y\)
实际上就是异或了 \(0...1...010...0\)
我们从线性基的角度来考虑,我们首先找到若干不交线性基 \(B\)
但是需要输出方案就是一个比较困难的事情,由于我们无法和正常线性基一样按位处理。
但是我们发现这若干基里 \((x,y)\) 不成环,也就是在点的控制关系中有拓扑关系,我们从拓扑序考虑每个点是否调整即可。
实际上这个就是题解里所说的从 \(\text{dfs}\) 树调整的思路
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 2000005
int T;
int n,m;
struct E{int x,y;E(int x_,int y_):x(x_),y(y_){};};
struct OE{int x,y,id;OE(int x_,int y_,int id_):x(x_),y(y_),id(id_){};};
bool operator < (OE A,OE B){return A.x < B.x;}
using std::vector;
vector<E>e[2];
vector<OE>bas;
int bas_id;
vector<OE>bas_node[N];
int vis_bas[N];
int in[N];
int fa[N];
inline int find(int x){return x == fa[x] ? x : (fa[x] = find(fa[x]));}
vector<int>G[N];
vector<int>S[N];
int nex[N];
int vis[N];
int inbas[N];
using std::stack;
stack<int>ans;
inline void dfs(int u){
for(;nex[u] < G[u].size();++nex[u]){
int v = G[u][nex[u]];
int ind = S[u][nex[u]];
if(vis[ind])continue;
vis[ind] = 1;dfs(v);
}
ans.push(u);
}
int cnt;
inline void print(){
puts("YES");
std::cout<<e[1].size()<<"\n";
for(int i = 1;i <= n;++i)G[i].clear(),S[i].clear(),nex[i] = 0;
cnt = 0;
for(auto em : e[1]){
int x = em.x,y = em.y;
G[x].push_back(y);G[y].push_back(x);
S[x].push_back(++cnt);S[y].push_back(cnt);
}
for(int i = 1;i <= cnt;++i)vis[i] = 0;
dfs(1);
while(ans.size()){std::cout<<ans.top()<<" ";ans.pop();}
puts("");
}
using std::queue;
queue<int>Q;
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d%d",&n,&m);
e[0].clear();e[1].clear();bas.clear();
for(int i = 1;i <= m;++i){
int x,y,op;
scanf("%d%d%d",&x,&y,&op);
e[op].emplace_back(x,y);
}
// puts("YES");
bas_id = 0;
for(int i = 1;i <= n;++i)in[i] = 0,inbas[i] = 0,bas_node[i].clear();
for(int i = 1;i <= n;++i)fa[i] = i;
for(auto em : e[1]){in[em.x] ^= 1 ;in[em.y] ^= 1;}
for(auto em : e[0]){
int x = em.x,y = em.y;
int fx = find(x),fy = find(y);
if(fx == fy){continue;}
fa[fx] = fy;
bas.emplace_back(x,y,++bas_id);
}
// for(auto em : bas){std::cout<<em.x<<" "<<em.y<<"\n";}
for(int i = 1;i <= bas_id;++i)vis_bas[i] = 0;
for(auto em : bas){
inbas[em.x] ++ ;inbas[em.y] ++ ;
bas_node[em.x].push_back(em);bas_node[em.y].push_back(em);
}
while(Q.size())Q.pop();
for(int i = 1;i <= n;++i){if(inbas[i] == 1)Q.push(i);}
while(Q.size()){
int u = Q.front();Q.pop();
for(auto em : bas_node[u]){
int x = u,y = em.x + em.y - u;
if(vis_bas[em.id])continue;
vis_bas[em.id] = 1;
if(in[x]){in[x] ^= 1;in[y] ^= 1;e[1].emplace_back(em.x,em.y);}
inbas[y] -= 1;if(inbas[y] <= 1){Q.push(y);}
}
}
bool flg = 1;
for(int i = 1;i <= n;++i){flg &= (!in[i]);}
if(flg){print();}else puts("NO");
}
}
**本文同步自 24暑期赛训合集
谢谢,你关注的鸽子博主更新了。
上赛季末段没能忍住网瘾, 转生成 ACMer 了
和队友一起拿了块邀请赛金牌和省赛冠军,下半年区域赛不想拖后腿所以还是得努努力啊。
但是因为博主还要跑科研实验 以及 机器人比赛的事情,所以大概一天只能看几个题
唉,被自己菜晕,能不能来点作用,别浪费队友开出来的题。
下列列出的 √ 为自己想出来的,× 为看了题解。
个人训练赛记录,CF / AT 为主
| 比赛 | sloved problem | rank |
|---|---|---|
| Codeforces Round #956 (Div. 2) and ByteRace 2024 | 4/7 | 802 |
| Codeforces Round 958 (Div. 2) | 3/6 | 1190 |
| Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2) | 5/8 | 806 |
| Codeforces Round 960 (Div. 2) | 4/7 | 368 |
wanna_be_free 训练赛记录,排名以 vp 榜为主
| 比赛 | sloved problem | rank |
|---|---|---|
| 2024 (ICPC) Jiangxi Provincial Contest | 10/12 | 10 |
| The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest | 12/13 | 5 |
| 2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site sloved problem(这场暂时先不写记录了吧,来不及写完了,回来补这场的时候再写) | 6/13 | 106 |
| 24牛客多校第一场 | 7/11 | 14 |
| 24牛客多校第二场 | 7/10 | 40 |
| 24牛客多校第三场 | 6/12 | 70 |
| 24牛客多校第四场 | 9/12 | 35 |
| 24牛客多校第五场 | 5/13 | 49 |
| 24牛客多校第六场 | 7/11 | 32 |
| 24牛客多校第七场 | 5/12 | 27 |
| 24牛客多校第八场 | 5/11 | 82 |
[√] 不知名题
求 \(p_i = (a p_{i - 1} ^ 2 + b p_{i - 1} + c) \mod m + \lfloor \frac{p_{i - 1}}{2} \rfloor\)
\(p_0 = k\) 时,求 \(p_{T}\)
\(k,T \leq 1e18\)
\(a,b,c,m \leq 5e5\)
看这个 \(m\) 显然就很能操作啊。
思考 \(p_i \leq 2m\) 时显然最后范围也在 \(2m\) 中,\(m\) 不大显然可以处理值域上的步数信息。
\(p_i > 2m\) 时,理论上 \(log_2{m}\) 步就进入范围了。
所以直接就倍增即可。
//明剑照霜,秋风走马
#include<bits/stdc++.h>
#define ll long long
#define M 600005
ll mod,a,b,c;
int T;
int A[M * 2][80];
ll mul[80];
signed main(){
// freopen("5.in","r",stdin);
// freopen("5.out","w",stdout);
scanf("%lld%lld%lld%lld",&mod,&a,&b,&c);
a = a % mod;
b = b % mod;
c = c % mod;
mul[0] = 1;
for(int i = 1;i <= 60;++i)mul[i] = mul[i - 1] * 2;
for(int i = 0;i <= 2 * mod;++i){A[i][0] = (1ll * a * (i % mod) * (i % mod) % mod+ 1ll * b * (i % mod) % mod + c) % mod + i / 2;}
for(int t = 1;t <= 60;++t){
for(int i = 0;i <= 2 * mod;++i){
A[i][t] = A[A[i][t - 1]][t - 1];
}
}
scanf("%lld",&T);
while(T -- ){
ll k,y;
scanf("%lld%lld",&k,&y);
while((k > 2 * mod) && y){--y;k = (1ll * a * (k % mod) * (k % mod) % mod + 1ll * b * (k % mod) % mod + c) % mod + k / 2;}
if(y == 0){std::cout<<k<<"\n";continue;}
ll now = k;
for(int t = 60;t >= 0;--t){if(y < 0)return 0;if(y >= mul[t]){now = A[now][t];y -= mul[t];}if(!y)break;}
std::cout<<now<<"\n";
}
}
另外 ll 求余 int 居然是 UB 吗,,,,
[√] [Usaco2008 Jan]猜数游戏
给若干区间 \([l,r]\) ,说明其最小值为 \(x\)
问最多前几个区间能够在数组每个都不一样的条件下自洽
首先第一眼看出来了能不能判全局合法,考虑每个相同权值的区间求交,由于每个数不一样则这个最小值一定出现在交集内,若没有交集则错
然后考虑从大权值加到小权值,若一个小权值的区间,在之前已经被大权值区间全部覆盖,则这个区间无法填数 (因为大权值限制条件显然更强)
然后想错了,上来写了个吉司机,一个区间一个区间加入,获得了 6.0 / 100.0 的好成绩
然后既然能判全局合法的方法,那我们直接二分 M 即可。
//山桃红花满上头,蜀江春水拍山流。
#include<bits/stdc++.h>
#define N 2000005
int t[N << 2];
int sum[N << 2];
#define ls(x) (x << 1)
#define rs(x) ((x << 1) | 1)
#define tag(x) t[x]
#define s(x) sum[x]
#define mid ((l + r) >> 1)
int n,q;
#define root 1,1,n
inline void push(int u,int l,int r){if(tag(u) == -1)return ;tag(ls(u)) = tag(rs(u)) = tag(u);s(u) = (r - l + 1) * tag(u);s(ls(u)) = (mid - l + 1) * tag(u);s(rs(u)) = (r - mid) * tag(u);tag(u) = -1;}
inline void up(int u){s(u) = s(ls(u)) + s(rs(u));}
inline void clear(int u,int l,int r){
tag(u) = -1;s(u) = 0;
clear(ls(u),l,mid);clear(rs(u),mid + 1,r);
}
inline void cover(int u,int l,int r,int lt,int rt,int p){
// std::cout<<"COVER "<<u<<" "<<l<<" "<<r<<" "<<lt<<" "<<rt<<" "<<p<<"\n";
if(lt <= l && r <= rt){tag(u) = p;s(u) = (r - l + 1) * tag(u);return ;}
push(u,l,r);
if(lt <= mid)cover(ls(u),l,mid,lt,rt,p);
if(rt > mid)cover(rs(u),mid + 1,r,lt,rt,p);
up(u);
}
inline int query(int u,int l,int r,int lt,int rt){
int ans = 0;
if(lt <= l && r <= rt){return s(u);}
push(u,l,r);
if(lt <= mid)ans += query(ls(u),l,mid,lt,rt);
if(rt > mid)ans += query(rs(u),mid + 1,r,lt,rt);
return ans;
}
struct P{int l,r,v;}A[N],p[N];
using std::map;
map<int,int>L,R;
bool operator < (P A,P B){return A.v > B.v;}
inline bool check(int lim){
for(int i = 1;i <= lim;++i)p[i] = A[i];
cover(root,1,n,0);
L.clear();R.clear();
std::sort(p + 1,p + lim + 1);
// std::cout<<lim<<"\n";
// for(int i = 1;i <= lim;++i)std::cout<<p[i].l<<" "<<p[i].r<<" "<<p[i].v<<"\n";
int las = 1;
for(int i = 1;i <= lim;++i){
//judge and
if(p[i].v != p[i - 1].v){
while(p[las].v != p[i].v){
cover(root,p[las].l,p[las].r,1);
++ las;
}
}
if(L[p[i].v] == 0){L[p[i].v] = p[i].l;R[p[i].v] = p[i].r;}
else {L[p[i].v] = std::max(L[p[i].v],p[i].l);R[p[i].v] = std::min(R[p[i].v],p[i].r);}
if(R[p[i].v] < L[p[i].v]){return 0;}
//judge cover
// std::cout<<"QUERY "<<L[p[i].v]<<" "<<R[p[i].v]<<"\n"<<query(root,L[p[i].v],R[p[i].v])<<"\n";
if(query(root,L[p[i].v],R[p[i].v]) == (R[p[i].v] - L[p[i].v] + 1))return 0;
}
return 1;
}
int main(){
scanf("%d%d",&n,&q);
for(int i = 1;i <= q;++i){scanf("%d%d%d",&A[i].l,&A[i].r,&A[i].v);}
int l = 1,r = q;
while(l + 1 < r){
// std::cout<<l<<" "<<r<<" "<<check(mid)<<"\n";
if(check(mid)){l = mid;}
else r = mid - 1;
}
int ans;
if(check(r))ans = r;
else if(check(l))ans = l;
if(check(q))ans = q;
if(ans == q)puts("0");else std::cout<<ans + 1<<"\n";
}
/*
20 4
1 10 7
5 19 7
3 12 8
11 15 12
*/
[x] k-Maximum Subsequence Sum
每次单点改
询问一个区间上 \(k\) 个非交子序列的最大和.
赛场上完全没懂,下来一看原来以前做过这个题,果然以前训练也太抽象了。
考虑使用反悔贪心的操作,每次选取一个区间最大子段和,然后把这一段取反,再取最大子段和,重复 \(k\) 次即可.
然后代码不是很好写,但也还行,就是比较麻烦,中间被没预设取反标志值坑了很久.
//山桃红花满上头,蜀江春水拍山流。
#include<bits/stdc++.h>
#define N 100005
struct seg{
int l,r,s;
seg(int l = 0,int r = 0,int s = 0):l(l),r(r),s(s){}
};
seg operator + (seg A,seg B){return seg(A.l,B.r,A.s + B.s);}
bool operator < (seg A,seg B){return (A.s < B.s);}
struct node{
seg lmi,lmx;
seg rmi,rmx;
seg mi,mx;
seg all;
bool rev;
}T[N << 2];
#define lmi(x) x.lmi
#define lmx(x) x.lmx
#define rmi(x) x.rmi
#define rmx(x) x.rmx
#define mx(x) x.mx
#define mi(x) x.mi
#define all(x) x.all
#define tag(x) x.rev
#define L(x) x.l
#define R(x) x.r
#define S(x) x.s
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
inline void init(node &x,int t,int p){
L(lmi(x)) = L(lmx(x)) = L(rmi(x)) = L(rmx(x)) = L(all(x)) = L(mi(x)) = L(mx(x)) = t;
R(lmi(x)) = R(lmx(x)) = R(rmi(x)) = R(rmx(x)) = R(all(x)) = R(mi(x)) = R(mx(x)) = t;
S(lmi(x)) = S(lmx(x)) = S(rmi(x)) = S(rmx(x)) = S(all(x)) = S(mi(x)) = S(mx(x)) = p;
tag(x) = 0;
}
node operator + (node A,node B){
node x;
lmi(x) = std::min(lmi(A),all(A) + lmi(B));
lmx(x) = std::max(lmx(A),all(A) + lmx(B));
rmi(x) = std::min(rmi(B),rmi(A) + all(B));
rmx(x) = std::max(rmx(B),rmx(A) + all(B));
mi(x) = std::min(rmi(A) + lmi(B),std::min(mi(A),mi(B)));
mx(x) = std::max(rmx(A) + lmx(B),std::max(mx(A),mx(B)));
all(x) = all(A) + all(B);
tag(x) = 0;
return x;
}
inline void prind(seg U){printf("[%d,%d],sum: %d\n",L(U),R(U),S(U));}
inline void print(node U,int l,int r){
puts("----------------------------");
printf("THE BLOCK OVER WITH [%d,%d] : \n",l,r);
printf("THE LMI:");prind(lmi(U));
printf("THE LMX:");prind(lmx(U));
printf("THE RMI:");prind(rmi(U));
printf("THE RMX:");prind(rmx(U));
printf("THE MI:");prind(mi(U));
printf("THE MX:");prind(mx(U));
printf("THE ALL:");prind(all(U));
puts("----------------------------");
}
inline void flip(node &x){
tag(x) ^= 1;
std::swap(lmi(x),lmx(x));std::swap(rmi(x),rmx(x));std::swap(mi(x),mx(x));
S(lmi(x)) *= -1;S(lmx(x)) *= -1;S(rmi(x)) *= -1;S(rmx(x)) *= -1;
S(mi(x)) *= -1;S(mx(x)) *= -1;
S(all(x)) *= -1;
}
inline void push(int u){
if(tag(T[u])){
flip(T[ls(u)]);
flip(T[rs(u)]);
tag(T[u]) = 0;
}
}
int n;
int a[N];
#define root 1,1,n
#define mid ((l + r) >> 1)
inline void build(int u,int l,int r){
if(l == r){init(T[u],l,a[l]);return ;}
build(ls(u),l,mid);build(rs(u),mid + 1,r);
T[u] = T[ls(u)] + T[rs(u)];
tag(T[u]) = 0;
}
inline void change(int u,int l,int r,int t,int p){
if(l == r){init(T[u],t,p);return ;}
push(u);
if(t <= mid)change(ls(u),l,mid,t,p);
if(t > mid)change(rs(u),mid + 1,r,t,p);
T[u] = T[ls(u)] + T[rs(u)];
}
inline void cover(int u,int l,int r,int tl,int tr){
if(tl <= l && r <= tr){
flip(T[u]);
return ;
}
push(u);
if(tl <= mid)cover(ls(u),l,mid,tl,tr);
if(tr > mid)cover(rs(u),mid + 1,r,tl,tr);
T[u] = T[ls(u)] + T[rs(u)];
}
inline node query(int u,int l,int r,int tl,int tr){
node ansA,ansB;
L(lmi(ansA)) = L(lmi(ansB)) = -1;
if(tl <= l && r <= tr)return T[u];
if(tl <= mid)ansA = query(ls(u),l,mid,tl,tr);
if(tr > mid)ansB = query(rs(u),mid + 1,r,tl,tr);
if(L(lmi(ansA)) == -1)return ansB;
if(L(lmi(ansB)) == -1)return ansA;
return ansA + ansB;
}
int q;
using std::queue;
std::queue<seg>U;
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%d",&a[i]);
build(root);
scanf("%d",&q);
while(q --){
int op;
scanf("%d",&op);
if(op == 0){int t,x;scanf("%d%d",&t,&x);change(root,t,x);}
if(op == 1){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int ans = 0;
for(int i = 1;i <= k;++i){
node segment = query(root,l,r);
if(S(mx(segment)) > 0){
ans += S(mx(segment));
cover(root,L(mx(segment)),R(mx(segment)));
U.push(mx(segment));
}
}
std::cout<<ans<<"\n";
while(U.size()){
seg top = U.front();
U.pop();
cover(root,L(top),R(top));
}
}
}
}
2024 (ICPC) Jiangxi Provincial Contest
24-7-5 12:00 - 17:00
下面指的是我和队友一起做的情况
[√]A
a + b + c
[√]C
发现如果加起来刚好等于 \(S\),则答案为 \(n\),否则答案为 \(n - 1\)
[√]D
gcd 是质因数次数取 min
lcm 是质因数次数取 max
死去的离散数学整除格正在攻击我
考虑实际上是把每一维质因数的次数排序。
因为 \(x + y \leq gcd(x,y) + lcm(x,y)\),所以最后每一维的应该都是排序好的。
质因数分解然后硬来就行了。
[√]G
队友写的,这里看看题解做法。
考虑每一位的取模贡献 \(11^x * a_x \mod 5\)
又因为 \(11 ^ x \mod 5 = 1\)
所以就是加起来取模 \(5\) 即可
[√]H
什么机器学习题
考虑卷积核上每一位的贡献在原矩阵上就是一个矩阵和,使用二维前缀和算出每一位贡献即可。
[√]J
麻将模拟题。
原本以为要判很多牌型,结果只有国士无双十三面和七对子。
队友看懂题之后就直接过了。
[√]K
考虑实际上等价于 \((1,1) \to (2,n)\) 的方案数,实际上的答案就是 \(2 ^ {n - 1}\)
[√]L
考虑 \(K\) 非常小,实际上只要每次在一个门开门的时候跑一遍单源最短路,在关门的时候把答案去掉即可。
[√]F
队友上来就toptree直接写了。
非常的牛。
我只会那个线段树分治加并查集的 2log 做法,但是鉴于我队友看起来是数据结构机器人,感觉很难有他不会的数据结构题,我就不写了。
[√]I
考虑到覆盖边其实是无所谓的条件。
转化成覆盖点。
然后考虑枚举所有的外接圆,一个圆可以由 \(3\) 个在圆上的点确定,或者 \(2\) 个直径上的点确定,直接 \(O(n^2 + n^3)\) 枚举即可。
然后考虑一个圆内能覆盖多少个点,然后统计答案即可。
[x]E
给定一个序列,求找出两个不同的子序列,要求两个子序列和相同,给出方案。
非常苦手,队友上来说应该是什么随机题,非常神秘。
首先知道一个长度 \(n\) 的序列,其子序列数为 \(2^n\)
其值域 \(S \in [0,A * n],v_i \leq A\)
那在 \(29 \leq n\) 时 存在 \(A * n \leq 2 ^ n\)
这说明如果一个序列长度大于等于 \(29\) 时,其一定存在两个不同子序列的和相同(鸽笼原理)
那么我们实际上只需要处理 \(30\) 长度的序列,如果长度大于三十,我们取前三十个元素即可。
接下来我们考虑如何处理 \(30\) 长度的序列了,我们考虑强制最终的答案不交,那么一个元素的处理方法就是丢到字序列A,丢到子序列B,和完全不动。
我们可以使用 mid in mid 的做法,我们处理前十五个的情况和后十五个的情况
容易想到的是我们直接就地使用一个 map,然后每个 map 的下标为 子序列A的和 与 子序列B的和 的差值,然后存的是对应一个状压的三进制数,其中每一位0/1/2表示这个元素没动/在A/在B。
那么我们在 mid in mid 最后统计结果,就是前十五个的 A - B 的差值是后十五个某方案的 A - B 差值的相反数,然后统计方案即可。
但是这样的复杂度大抵是 \(O(3^{\min(15,\frac{n}{2})}\log(3^{\min(15,\frac{n}{2})}))\)
有点通过不了。
这里其实有很多种处理方法,后面会提到,先介绍题目所说的三个序列归并的方法。
我们考虑我们在搜索状压的时候,做的其实是二元状态 \((s,d)\) 的变更
我们从前 \(i - 1\) 个元素的方案加入第 \(i\) 个元素,然后考虑更新方案
实际上是
\((s,d) \to (s \times 3,d)\)
\((s,d) \to (s \times 3 + 1,d + a_i)\)
\((s,d) \to (s \times 3 + 2,d - a_i)\)
分别对应上述三种加入方案
那么我们只要在加入到第\(i-1\)元素,就把所有的 \(3^{i-1}\) 的二元状态按照 \(d\) 排好序,那么我们加入第 \(i\) 个元素的时候,实际上是对上面三种序列归并排序即可。
你可以直接把三个序列先分别求出来再排序,也可以直接维护三个头指针,三指针移动的把三种对应的最小的先放进 \(3^i\) 个状态对应的答案序列里,然后加对应指针向后挪一位即可。
然后我们得到了前十五个的状态,和后十五的状态,由于均有序,我们直接就双指针查有没有相反数即可。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 1000000
#define M 43046721
int T;
int a[N];
int n;
struct P{
int s_val;int dval;
P(int sv = 0,int dv = 0):s_val(),dval(){};
};
bool operator < (P A,P B){return A.dval < B.dval;}
bool operator == (P A,P B){return A.s_val == B.s_val && A.dval == B.dval;}
void init(P &A,int sv,int dv){A.s_val = sv;A.dval = dv;}
using std::vector;
vector<P>S[2];
/*
0 contain A - B
1 contain B - A
*/
#define inf 5000000000
inline void print(int len,int ind){
for(auto [s_val,dval] : S[ind]){
for(int i = 0;i < len;++i){std::cout<<s_val % 3<<" ";s_val /= 3;}
std::cout<<"val :"<<dval<<"\n";
}
}
inline void del(int l,int r,int ind){//del with [l,r]
int len = r - l + 1;
S[ind].clear();
S[ind].emplace_back(0,0);
for(int i = l;i <= r;++i){
int tnull = 0,tA = 0,tB = 0;
vector<P>ans;
while(tnull < S[ind].size() || tA < S[ind].size() || tB < S[ind].size()){
P cnull,cA,cB;
if(tnull < S[ind].size())init(cnull,S[ind][tnull].s_val * 3,S[ind][tnull].dval);else init(cnull,0,inf);
if(tA < S[ind].size())init(cA,S[ind][tA].s_val * 3 + 1,S[ind][tA].dval + (ind == 0 ? 1 : -1) * a[i]);else init(cA,0,inf);
if(tB < S[ind].size())init(cB,S[ind][tB].s_val * 3 + 2,S[ind][tB].dval + (ind == 0 ? -1 : 1) * a[i]);else init(cB,0,inf);
P minn = std::min(cnull,std::min(cA,cB));
if(cnull == minn){ans.push_back(cnull);tnull ++ ;}
if(cA == minn){ans.push_back(cA);tA ++ ;}
if(cB == minn){ans.push_back(cB);tB ++ ;}
}
S[ind] = ans;
}
// print(len,ind);
}
vector<int>ans[3];
inline void find(int l,int r,int St){
for(int i = r;i >= l;--i){
ans[St % 3].push_back(i);
St /= 3;
}
}
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i){scanf("%d",&a[i]);}
if(n == 1){puts("-1");continue;}
// puts("Yes");
del(1,std::min(15,n / 2),0);del(std::min(16,n / 2 + 1),std::min(30,n),1);
int tpre = 0,tend = 0;
bool flg = 0;
ans[0].clear();ans[1].clear(),ans[2].clear();
while(tpre < S[0].size() && tend < S[1].size()){
if(S[0][tpre].dval == S[1][tend].dval && S[0][tpre].s_val != 0){
flg = 1;
find(1,std::min(15,n / 2),S[0][tpre].s_val);
find(std::min(16,n / 2 + 1),std::min(30,n),S[1][tend].s_val);
break;
}
if(S[0][tpre] == std::min(S[0][tpre],S[1][tend]))tpre ++ ;else tend++;
}
if(!flg)puts("-1");
if(flg){
std::cout<<ans[1].size()<<" ";
for(auto v : ans[1])std::cout<<v<<" ";
puts("");
std::cout<<ans[2].size()<<" ";
for(auto v : ans[2])std::cout<<v<<" ";
puts("");
}
}
}
[√]忘情
求给定一序列,将其分为 \(m\) 段,每段代价为 \(\frac{(\sum_{i = 1}^{n}x_i \times \overline{x})}{\overline{x}^2}\),求最小代价
容易推导到代价实际为 \((1 + \sum x_i)^2\)
如果没有 \(m\) 段的代价实际上,我们可以直接使用斜率优化完成这个题
\(j \to i : f_i = f_j + (S_i - S_j + 1) ^ 2 \to f_i = f_j + S_i^2 - 2\times S_i\times (S_j - 1) + (S_j - 1) ^ 2\)
\(2\times S_i \times (S_j - 1) + f_i - S_i ^ 2 = f_j + (S_j - 1)^2\)
即存在一条斜率为 \(2\times S_i\) 的直线通过 \((S_j - 1,f_j + (S_j - 1)^2)\)
由于要 \(f_i\) 最小,则是求最小截距,那即为维护下凸包单调队列即可
接着考虑如何强制选 \(m\) 段
这是一个经典的条件,我们考虑 \(g_k\) 表示分了 \(k\) 段的答案,其显然是一个上凸包
我们考虑二分凸包斜率,找到 \(m\) 所在的凸包位置即可。
然后这个斜率我们以 \((k,lk)\) 的形式表示,其实即为每次转移附带了一个 \(l\) 的代价
然后我们返回可以分的段数
这里需要注意可能同一斜率上有多个点,这里应该返回最小的段数/最大的段数,后面二分自洽即可,最后的答案减去 \(m\times l\)
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 100005
int n,m;
int a[N];
ll pre[N];
int minn = 2000;
ll f[N];
int t[N];
struct P{
ll x;ll y;int time;
P(ll x_val = 0,ll y_val = 0,int t = 0):x(x_val),y(y_val),time(t){};
};
P s[N];
int top,end;
inline void print(P A){std::cout<<"( "<<A.x<<" "<<A.y<<" "<<A.time<<")"<<"\n";}
inline int check(ll d){
top = end = 1;
s[end] = P(-1,1 + d);
for(int i = 1;i <= n;++i)f[i] = 0;
for(int i = 1;i <= n;++i){
while((end > top) && (2 * pre[i] * (s[top + 1].x - s[top].x) > (s[top + 1].y - s[top].y)))++top;
f[i] = s[top].y - 2 * pre[i] * s[top].x + pre[i] * pre[i];
t[i] = s[top].time + 1;
P now(pre[i] - 1,(pre[i] - 1) * (pre[i] - 1) + d + f[i],t[i]);
while(end > top && (now.y - s[end].y) * (s[end].x - s[end - 1].x) < (s[end].y - s[end - 1].y) * (now.x - s[end].x))--end;
s[++end] = now;
}
return t[n];
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i){scanf("%d",&a[i]);pre[i] = pre[i - 1] + a[i];minn = std::min(a[i],minn);}
ll l = 0,r = 1e18;
#define mid ((l + r) >> 1)
while(l + 1 < r){if(check(mid) > m)l = mid;else r = mid;}
ll ansd = 0;
if(check(l) <= m)ansd = l;
else if(check(r) <= m)ansd = r;
int used = check(ansd);
std::cout<<f[n] - 1ll * m * ansd<<"\n";
}
[√]CF1499F Diameter Cuts
给定一颗 \(n\) 个节点的树和一个正整数 \(k\),求多少种删边集合,让删边后的所有连通块直径不大于 \(k\)
令 \(f_{i,j}\) 为 \(i\) 到父亲的边不断,其父亲到 \(i\) 子树内最长路径为 \(j\) 的方案数
特殊的 \(f_{i,0}\) 表示断开 \(i\) 和父亲的边
考虑如何统计答案,实际上就是任两子树内最长路径相加不大于 \(k\) 的方案数
考虑如果显然子树内不能存在两条大于 \(\frac{k}{2}\) 的链,先对所有子树前缀统计,然后枚举大于 \(\frac{k}{2}\) 的在哪个子树,然后其余的所有子树限制为 \(k - l\),\(l\) 为枚举的最长链。
若不存在大于 \(\frac{k}{2}\) 正常前缀算答案即可。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 5005
#define mod 998244353
int f[N][N];// n longest n
int pre[N][N];// sum of pre
int fa[N];
int ps[N];
using std::vector;
vector<int>G[N];
int n,k;
inline int qpow(int x,int t){
int a = x;
int ans = 1;
while(t){
if(t & 1)ans = 1ll * ans * a % mod;
a = (1ll * a * a) % mod;
t >>= 1;
}
return ans;
}
inline int inv(int x){return qpow(x,mod - 2);}
inline void dfs(int u = 1,int ffa = 0){
fa[u] = ffa;
for(auto v : G[u]){
if(v == fa[u])continue;
dfs(v,u);
}
}
inline void del(int u = 1){
for(int i = 0;i <= k;++i)ps[i] = 1;
for(auto v : G[u]){
if(v == fa[u])continue;
del(v);
}
for(int i = 0;i <= k;++i)ps[i] = 1;
for(auto v : G[u]){
if(v == fa[u])continue;
for(int i = 0;i <= k;++i)ps[i] = 1ll * ps[i] * pre[v][i] % mod;
}
/*connected*/
for(int i = 0;i <= k / 2;++i)f[u][i + 1] = (ps[i] - (i != 0 ? ps[i - 1] : 0) + mod) % mod;
/*unconneted*/
f[u][0] = 0;
for(auto v : G[u]){
if(v == fa[u])continue;
for(int i = k / 2 + 1;i <= k;++i){
int now = 1ll * f[v][i] * ps[k - i] % mod * inv(pre[v][k - i]) % mod;
f[u][0] = (1ll * f[u][0] + now) % mod;
f[u][i + 1] = (1ll * f[u][i + 1] + now) % mod;
}
}
f[u][0] = (f[u][0] + ps[k / 2]) % mod;
pre[u][0] = f[u][0];
for(int i = 1;i <= k + 1;++i)pre[u][i] = (pre[u][i - 1] + f[u][i]) % mod;
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs();
del();
std::cout<<f[1][0]<<"\n";
}
Codeforces Round #956 (Div. 2) and ByteRace 2024
[√]A
构造一个 \((\sum_{i | k}a_i) | k\) 的数列
直接输出 \(1,...,n\) 即可
[√]B
给定两矩阵 \(A,B\),每次可以选择一个矩形的四个角,左上角和右下角加 \(1\),右上角左下角加 \(2\),所有元素对 3 取模
考虑到实际上每行每列的对 \(3\) 取模不变,容易证明其为充要条件。
充分条件可以考虑每行调整即可。
[√]C
给定 \(n\) 元序列 \(A,B,C\), 每个序列和均为 \(sum\) , 求把 \(A,B,C\) 取三个下标不交的子段,要求每个子段和大于 \(\frac{1}{3} sum\)
考虑枚举中间那段在哪,然后双指针即可,考虑显然是前一段为 \([1,l - 1]\),后一段为 \([r + 1,n]\)
判断是否满足条件即可
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 1000005
int n;
int a[N][3];
ll prea[N][3];
ll enda[N][3];
ll tot;
int T;
int ans[2][3];
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int j = 0;j <= 2;++j)
for(int i = 1;i <= n;++i)scanf("%d",&a[i][j]);
for(int j = 0;j <= 2;++j)
for(int i = 0;i <= n + 1;++i)prea[i][j] = enda[i][j] = 0;
for(int j = 0;j <= 2;++j)
for(int i = 1;i <= n;++i)prea[i][j] = prea[i - 1][j] + a[i][j];
for(int j = 0;j <= 2;++j)
for(int i = n;i >= 1;--i)enda[i][j] = enda[i + 1][j] + a[i][j];
bool flg = 0;
ll target = std::ceil(1.0 * prea[n][0] / 3);
// std::cout<<target<<"\n";
for(int j = 0;j <= 2;++j){
// std::cout<<"check "<<j<<"\n";
int t1 = (j + 1) % 3,t2 = (j + 2) % 3;
int ta = 1;
for(int i = 2;i <= n;++i){
while(prea[i][j] - prea[ta][j] >= target)ta ++ ;
// std::cout<<ta<<" "<<i<<"\n";
// std::cout<<t1<<" "<<prea[ta - 1][t1]<<" "<<t2<<" "<<enda[i + 1][t2]<<"\n";
if(prea[ta - 1][t1] >= target && enda[i + 1][t2] >= target){
ans[0][t1] = 1,ans[1][t1] = ta - 1;
ans[0][j] = ta,ans[1][j] = i;
ans[0][t2] = i + 1,ans[1][t2] = n;
flg = 1;
break;
}
std::swap(t1,t2);
// std::cout<<t1<<" "<<prea[ta - 1][t1]<<" "<<t2<<" "<<enda[i + 1][t2]<<"\n";
if(prea[ta - 1][t1] >= target && enda[i + 1][t2] >= target){
ans[0][t1] = 1,ans[1][t1] = ta - 1;
ans[0][j] = ta,ans[1][j] = i;
ans[0][t2] = i + 1,ans[1][t2] = n;
flg = 1;
break;
}
}
if(flg)break;
}
if(flg){
for(int i = 0;i <= 2;++i)
for(int j = 0;j <= 1;++j)std::cout<<ans[j][i]<<" ";
puts("");
}else puts("-1");
}
}
[√]D
给定两个序列 \(A,B\),每次可以在 \(A\) 和 \(B\) 中选择两个下标差相同的\((A_i,A_j),(B_p,B_q)\),将 \(A_i,A_j\) 值交换,\(B_p,B_q\) 交换
问能否最后相同。
显然权值集合要相同
然后跨越 \(d\) 的交换可以拆分成若干相邻交换,相邻交换逆序对奇偶改变
所以充要条件是 \(A,B\) 的逆序对奇偶相同
考虑充分条件构造,直接把一个调整到 \(1,...,n\),然后调整另外一边,此时另外一遍调整一定是偶数次,在调整好的一直选中相邻两个交换即可。
[√]F
给定一个序列 \(A\),其中一个区间的代价为 $val_{l,r} = \min_{l\leq x,y\leq r} A_X \oplus A_y $
问区间代价第 \(k\) 个为多少。
先二分
然后考虑如何计算 \(\leq mid\) 的答案
我们考虑枚举子点对的右端点 \(r\),我们找到离他最近的点 \(l\), 满足 \(A_r \oplus A_l \leq mid\),那么包含其的区间数量为 \([1,l]\times[r,n]\)
考虑如何统计所有的数量,你显然可以 \(2log\) 来矩阵并,这样你就爆爆了
我们考虑可以扫描线,我们双指针维护前缀最大的 \(l\),这个点的最近的点对为 \(l'\),那么新增的答案为 \(\max(l' - l,0)\times (n - r + 1)\)
只有 \(l' > l\) 有贡献,那么我们直接维护前缀指针 \(l\) ,每次尝试暴力向右移动即可,判断 \([l,r]\) 中是否右\(A_i \oplus A_r \leq mid\)
使用 01-tire 来进行查询过程,两支 \(log\)
也有一个 \(log\) 的做法,直接每位判定,但是主播比较懒,这个就不写了,当时比赛打一半去看 石油杯了。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 100005
#define inf 2000000000
#define int ll
using std::multiset;
int T;
int n,k;
int a[N];
int A[N * 50][2];
int siz[N * 50];
int cnt = 1;
inline void insert(int x){
int rt = 1;
for(int i = 31;i >= 0;--i){
siz[rt] ++ ;
int t = ((x >> i) & 1);
if(!A[rt][t])A[rt][t] = ++cnt;
rt = A[rt][t];
}
siz[rt] ++ ;
}
inline void erase(int x){
int rt = 1;
if(x == -1)return ;
for(int i = 31;i >= 0;--i){
siz[rt] -- ;
int t = ((x >> i) & 1);
if(!A[rt][t])A[rt][t] = ++cnt;
rt = A[rt][t];
}
siz[rt] -- ;
}
inline int find(int x){
int rt = 1;
int ans = 0;
if(siz[rt] == 0)return inf;
for(int i = 31;i >= 0;--i){
int t = ((x >> i) & 1);
if(siz[A[rt][t]])rt = A[rt][t];
else rt = A[rt][t ^ 1],ans |= (1ll << i);
}
return ans;
}
int lasl[N];
inline int check(int x){
int l = 0;
int ans = 0;
a[0] = -1;
for(int i = 1;i <= n;++i){
bool flg = (find(a[i]) <= x);
if(flg){
while(find(a[i]) <= x){erase(a[l ++ ]);}
insert(a[-- l]);
ans = ans + (l - lasl[i - 1]) * (n - i + 1);
}
lasl[i] = l;
insert(a[i]);
}
while(l <= n){erase(a[l ++ ]);}
return ans;
}
inline void slove(){
int l = 0,r = inf;
#define mid ((l + r) >> 1)
while(l + 1 < r){
if(check(mid) >= k)r = mid;
else l = mid + 1;
}
if(check(l) >= k)std::cout<<l<<"\n";
else std::cout<<r<<"\n";
}
signed main(){
scanf("%lld",&T);
while(T -- ){
scanf("%lld%lld",&n,&k);
for(int i = 1;i <= n;++i)scanf("%lld",&a[i]);
slove();
}
}
[x]E
我们只先计算先手球的期望。
首先思考到所有的特殊球和普通球的贡献是分开的
然后普通球的贡献是好计算的,$\lfloor\frac{c + 1}{2} \rfloor\frac{\sum_{i \in C} a_i}{c} $
然后思考特殊球的贡献,我当时以为是分成两段,但是后来看来是用 \(c\) 个普通球,分成了 \(c + 1\) 段,每人按顺序取,所以特殊球的期望为 \(\lfloor\frac{c + 2}{2}\rfloor \frac{\sum_{i \in S}a_i}{c + 1}\)
The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest
[√]A
考虑根号操作只有 \(\sqrt x\) 个数,若平方完再根号,对于不同的数没有任何贡献。
那么考虑先对 \(a\) 取下取整根号操作得到 \(b\),若 \(b^2 = a\) 则无贡献,否则有剩余贡献即可。
[√]D
队友发现答案一直是lose,输出 \(n\) 个 lose 即可。
[√]E
要求 \(B = \text{popcount}(A) + \text{popcount}(B)\mod 2^k\)
考虑反过来 \(B\) 固定时,\(\text{popcount}(A)\) 唯一即可
[√]M
考虑如何不重复的计这个,考虑枚举中间按两个点,然后尝试枚举下面矩形点对,然后正常统计即可。队友写了各种叉积判断避免了精度问题。
[√]H
给定 \(n\) 个点的树,\(m\) 个点对,可选择一个点为根,要求所有 \(2m\) 个点到它们对应的 \(LCA\) 最大距离最小。
考虑二分答案,对于一个点对的方案形为 \(k\) 极祖先的子树内/外。
考虑其为 \(\text{dfs}\) 序的 O(1) 段区间,查询是否交集为空即可。
[√]I
考虑最后一个完整排列的 \(f_i\)
\(f_i = \sum_{j = 1}^k f_{i - j} \times g_j\)
其中 \(g_j\) 为前缀 \(j\) 均不为子排列的方案数。
这个考虑容斥即可
\(g_i = i! - \sum_{j = 1}^{i - 1} j! \times g_{i - j}\)
[√]K
考虑一层层的从右到左的放置 \(r_i\)
下一层查询在上一层的左端点和其离的最近的 \(r_i\),判断若 \(l = l'\),则需要 \(r = r' - 1\) 否则 \(r = r'\)
[√]L
考虑先取所有最小单位 \(()\) ,然后依次打括号即可
[√]F
若 \(\frac{p}{q}\) 为有限小数,统计 \(p\) 。
对 \(q\) 的质因子 \(p_i\) ,其次幂小于等于 \(p\) 的幂次
考虑直接 \(\text{dfs}\) 即可。
[√]G
考虑对出现次数根号分治即可
含有出现次数大的那边离线下来做,小的直接暴力。
[√] B
实际上每一层都实际上依赖于第二层
假设二级一开始是工作状态,则一级一开始是不工作状态,三级一开始是辅助供能状态。
注意到三级的供能站想要转为供能状态,则需要二级处于不工作状 态;一级供能站想要转为供能状态,则需要二级处于辅助供能状态。
考虑将第二层拆成 供能转不工作 不工作转辅助
然后将有需求的向 供能转不工作 连边
一级功能向两者同时连,均为无穷,最小割割不掉即可。
复杂度正确性存疑。
[×] C
思考到:
你第一次改成了什么,最后就是什么
两个方向的答案是一样的
那么考虑如何快速算答案,双指针预处理出 \(f_{i,{0/1}}\) 为区间 \([i,(i + k - 1) \mod n]\) 变成\(0/1\) 的下一步操作该到哪里。
然后枚举第一步在哪,然后倍增即可。
代码暂无
Codeforces Round 958 (Div. 2)
[√]A
显然每次都扩展 \(k - 1\) 个 1
[√]B
考虑可以先把所有的长度大于 \(1\) 的 \(0\) 串先变成长度为 \(1\) 的。
接着检查是否 \(1\) 为剩下串的众数即可。
[√]C
考虑到要求相邻 \(\text{or}\) 的结果为 \(n\),那么即所有数的二进制下 \(1\) 集合为 \(n\) 的子集。
又要求严格递增,那么考虑依次从低位去掉 \(1\) 即可。
[√] D
又是一场想完没写完的做法。
场上一直写的是线性做法,我说怎么大家过的都这么快。
首先题意要求每次删除若干不连通点,最后求每个回合中的点权和。
考虑我们在第 \(i\) 回合删除了点 \(x\),他对最后答案的贡献为 \(i \times a_x\) 。
那么我们将题目转化为我们需要为每个点赋上一个权 \(b_i\),要求相邻点的赋权不相同,最小化 \(\sum b_i \times a_i\)
我们容易写出 \(f_{i,j}\) 表示 \(i\) 点赋权 \(j\),子树内 \(\sum b_x \times a_x\) 的最小和。
容易写出转移式即 \(f_{i,j} = \sum_{v \in son_i} \min_{k \neq j} f_{v,k} + j \times a_i\)
我们知道 \(b_i \leq n\) ,但是 \(n\) 只是我们随手估的一个不太好的上界,在官方题解中给出了一个更好的上界 \(b_i \leq \log n\),于是可以在 \(O(n\log^2 n)\) 或者 \(O(n\log n)\) 中解决这个问题。
但是我们有完全不需要基于上界的做法。
我们思考 \(\min_{k\neq j} f_{v,k}\) 这个函数,实际上的值是一个分类函数。
我们设 \(s\),\(f_{v,s} = \min f_{v,k}\)。
同时设 \(s'\),\(f_{v,s'} = \min_{k \neq s}f_{v,s'}\)。
即 \(v\) 的最小值和次小值点。
那么 \(\begin{equation} \min_{k \neq j} f_{v,k}=\left\{ \begin{aligned} f_{v,s} \quad j \neq s\\ f_{v,s'} \quad j = s\\ \end{aligned} \right . \end{equation}\)
于是我们发现实际上 \(\sum_{v \in son_i} \min_{k \neq j} f_{v,k}\) 只有至多 \(|son_i| + 1\) 种取值,即我们取任一子树的最小值对应的点,和不取任何一个所对应的值,这里我们称儿子的最小值的点的集合为 \(S\) 。
那么我们发现我们实际上并不需要维护所有点,我们只需要维护一个点的最小值和次小值位置即可。
那么我们接着来考虑后面这个 \(j \times a_i\) 函数,其为一个递增函数。我们考虑我们取任一子树的最小值对应的点,\(j\) 是固定无法修改的。但是对 \(j \not \in S\) ,我们只需要取 \(\text{mex}(S)\) 和 \(\text{mex}(S + \text{mex}(S))\) 。
在 \(|son_i| + 2\) 种答案中就能取到最小值和次小值。
这里我们采取了直接暴力一点的做法我直接取了 \(S,S + 1,S + 2\) 三种情况,在 \(3|son_i|\) 种情况取最小值和次小值,容易证明 \(\text{mex}(S) + \text{mex}(S + \text{mex}(S)) \in \{S + 1\} \cup \{S + 2\}\) 。
同时我在代码里使用 \(\text{map}\),可以使用 数组 或 \(\text{Hash}\) 表等其他线性映射结构来替代掉这个 \(\text{log}\) 。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 5000005
int T;
ll a[N];
using std::vector;
vector<int>G[N];
int n;
struct P{int ind;ll val;}minn[N],minm[N];
bool operator < (P A,P B){return A.val < B.val;}
bool operator <= (P A,P B){return A.val <= B.val;}
using std::map;
map<int,ll>dval;
vector<int>check;
inline void dfs(int u,int fa){
if(G[u].size() == 0 || (G[u].size() == 1 && G[u][0] == fa)){minn[u].ind = 1;minn[u].val = a[u];minm[u].ind = 2;minm[u].val = 2 * a[u];return ;}
for(auto v : G[u]){
if(v == fa)continue;
dfs(v,u);
}
dval.clear();check.clear();
ll allval = 0;
check.push_back(0);
for(auto v : G[u]){
if(v == fa)continue;
check.push_back(minn[v].ind);
allval += minn[v].val;
dval[minn[v].ind] += minm[v].val - minn[v].val;
}
P nowminn,nowminm;
nowminn.ind = nowminm.ind = 0;
nowminn.val = nowminm.val = 4e18;
for(int i = 1;i < check.size();++i){
ll x = check[i];
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
for(int i = 0;i < check.size();++i){
ll x = check[i] + 1;
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn ){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
for(int i = 0;i < check.size();++i){
ll x = check[i] + 2;
P now;now.ind = x;now.val = allval + x * a[u];
if(dval.find(x) != dval.end()){now.val += dval[x];}
if(now.ind != nowminn.ind && now <= nowminn ){nowminm = nowminn;nowminn = now;}
else if(now.ind != nowminn.ind && now < nowminm){nowminm = now;}
}
minn[u] = nowminn,minm[u] = nowminm;
}
signed main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i)scanf("%lld",&a[i]);
for(int i = 1;i <= n;++i)G[i].clear();
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,0);
std::cout<<minn[1].val<<"\n";
}
}
Codeforces Round 959 sponsored by NEAR (Div. 1 + Div. 2)
[√]A
令每个元素为其加一即可。
[√]B
如果前缀有一个地方是 \(1\) 即可.
for(int i = 1;i <= n;++i){
if(s[i] == 1)break;
if(s[i] != t[i]){flg = 0;break;}
}
flg ? puts("YES") : puts("NO");
[√]C
对每个点维护从这个点 \(0\) 有多少答案。
然后直接双指针,维护到后缀哪里变 \(0\) 即可。
[√]D
很有意思的一题,大胆猜测最后一定能连成一颗树
这里给定一个证明,考虑轮数从大到小
令 \(A\) 为全点集,令 \(S\) 为已经加入到生成树中的点,则我们假设 \([k,n]\) 轮都能满足新加一个点进 \(S\)
则在 \(k - 1\) 轮,我们考虑 \(|S| = n - k + 1,|A - S| = k - 1\)
则依据鸽巢原理定 \(x \in A - S,y \in S,x \mod k = y\mod k\)
那么知道这个那直接连边即可。
[√]E
很好的诈骗题,让我头脑旋转
考虑每颗树可以得到 \([0,s]\) 的任意树,实际上就是所有的值 \(or\) 起来最大即可。
[x]F
回忆欧拉回路的条件,每个点的度数都为偶数。
考虑转成奇偶,那么实际上就是给定了一个初态 \(S\),判断若干 \((x,y)\),每次操作形如取反 \(S_x,S_y\)
实际上就是异或了 \(0...1...010...0\)
我们从线性基的角度来考虑,我们首先找到若干不交线性基 \(B\)
但是需要输出方案就是一个比较困难的事情,由于我们无法和正常线性基一样按位处理。
但是我们发现这若干基里 \((x,y)\) 不成环,也就是在点的控制关系中有拓扑关系,我们从拓扑序考虑每个点是否调整即可。
实际上这个就是题解里所说的从 \(\text{dfs}\) 树调整的思路
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
#define N 2000005
int T;
int n,m;
struct E{int x,y;E(int x_,int y_):x(x_),y(y_){};};
struct OE{int x,y,id;OE(int x_,int y_,int id_):x(x_),y(y_),id(id_){};};
bool operator < (OE A,OE B){return A.x < B.x;}
using std::vector;
vector<E>e[2];
vector<OE>bas;
int bas_id;
vector<OE>bas_node[N];
int vis_bas[N];
int in[N];
int fa[N];
inline int find(int x){return x == fa[x] ? x : (fa[x] = find(fa[x]));}
vector<int>G[N];
vector<int>S[N];
int nex[N];
int vis[N];
int inbas[N];
using std::stack;
stack<int>ans;
inline void dfs(int u){
for(;nex[u] < G[u].size();++nex[u]){
int v = G[u][nex[u]];
int ind = S[u][nex[u]];
if(vis[ind])continue;
vis[ind] = 1;dfs(v);
}
ans.push(u);
}
int cnt;
inline void print(){
puts("YES");
std::cout<<e[1].size()<<"\n";
for(int i = 1;i <= n;++i)G[i].clear(),S[i].clear(),nex[i] = 0;
cnt = 0;
for(auto em : e[1]){
int x = em.x,y = em.y;
G[x].push_back(y);G[y].push_back(x);
S[x].push_back(++cnt);S[y].push_back(cnt);
}
for(int i = 1;i <= cnt;++i)vis[i] = 0;
dfs(1);
while(ans.size()){std::cout<<ans.top()<<" ";ans.pop();}
puts("");
}
using std::queue;
queue<int>Q;
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d%d",&n,&m);
e[0].clear();e[1].clear();bas.clear();
for(int i = 1;i <= m;++i){
int x,y,op;
scanf("%d%d%d",&x,&y,&op);
e[op].emplace_back(x,y);
}
// puts("YES");
bas_id = 0;
for(int i = 1;i <= n;++i)in[i] = 0,inbas[i] = 0,bas_node[i].clear();
for(int i = 1;i <= n;++i)fa[i] = i;
for(auto em : e[1]){in[em.x] ^= 1 ;in[em.y] ^= 1;}
for(auto em : e[0]){
int x = em.x,y = em.y;
int fx = find(x),fy = find(y);
if(fx == fy){continue;}
fa[fx] = fy;
bas.emplace_back(x,y,++bas_id);
}
// for(auto em : bas){std::cout<<em.x<<" "<<em.y<<"\n";}
for(int i = 1;i <= bas_id;++i)vis_bas[i] = 0;
for(auto em : bas){
inbas[em.x] ++ ;inbas[em.y] ++ ;
bas_node[em.x].push_back(em);bas_node[em.y].push_back(em);
}
while(Q.size())Q.pop();
for(int i = 1;i <= n;++i){if(inbas[i] == 1)Q.push(i);}
while(Q.size()){
int u = Q.front();Q.pop();
for(auto em : bas_node[u]){
int x = u,y = em.x + em.y - u;
if(vis_bas[em.id])continue;
vis_bas[em.id] = 1;
if(in[x]){in[x] ^= 1;in[y] ^= 1;e[1].emplace_back(em.x,em.y);}
inbas[y] -= 1;if(inbas[y] <= 1){Q.push(y);}
}
}
bool flg = 1;
for(int i = 1;i <= n;++i){flg &= (!in[i]);}
if(flg){print();}else puts("NO");
}
}
[x] G
考虑每一位的进位不会超过 \(n\)
令后续进位大小为 \(k\) ,则这一位进位至多 \(\frac{n + k}{2}\)
我们可以考虑 \(k \leq n\) 则 \(\frac{n + k}{2} \leq n\)
则这个地方我们直接暴力设计 \(f_{i,j}\) 为第 \(i\) 位进位 \(j\) 的方案即可。
所以在设计 \(\text{dp}\) 的时候不妨大胆一点,一下子跳过一些东西就容易忽略其他优化方法。
//樱花落尽阶前月,象床愁倚薰笼
#include<bits/stdc++.h>
#define ll long long
int T;
int n,k;
using std::vector;
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d%d",&n,&k);
std::string s;
vector<std::string>A(n + 1);
vector<vector<bool>>f(k + 1,vector<bool>(4 * n));
vector<vector<int>>g(k + 1,vector<int>(4 * n));
vector<vector<int>>las(k + 1,vector<int>(4 * n));
std::cin>>s;
f[k][0] = 1;
for(int i = 0;i <= k - 1;++i)
for(int j = 0;j <= 2 * n;++j)f[i][j] = 0;
for(int i = 1;i <= n;++i)std::cin>>A[i];
for(int i = k - 1;i >= 0;--i){
int now = s[i] - '0';
int cnt[2];
cnt[0] = cnt[1] = 0;
for(int j = 1;j <= n;++j){cnt[A[j][i] - '0'] ++ ;}
int t0 = cnt[1],t1 = cnt[0];
// std::cout<<"del "<<i<<"\n";
// std::cout<<now<<" "<<cnt[0]<<" "<<cnt[1]<<"\n";
for(int sum = 0;sum <= 2 * n;++sum){
if(f[i + 1][sum]){
int res0 = (t0 + sum) % 2;
int res1 = (t1 + sum) % 2;
// std::cout<<"LAS "<<sum<<" "<<res0<<" "<<res1<<"\n";
if(res0 == now){f[i][(t0 + sum) / 2] = 1;g[i][(t0 + sum) / 2] = 0;las[i][(t0 + sum) / 2] = sum;}
if(res1 == now){f[i][(t1 + sum) / 2] = 1;g[i][(t1 + sum) / 2] = 1;las[i][(t1 + sum) / 2] = sum;}
}
}
}
if(!f[0][0]){puts("-1");continue;}
int now = 0;
for(int i = 0;i <= k - 1;++i){
std::cout<<g[i][now];
now = las[i][now];
}
puts("");
}
}
Codeforces Round 960 (Div. 2)
[√]A
大胆猜测若有一种为奇数则可以
[√]B
简单构造,可以看代码
Submission #271558205 - Codeforces
[√]C
Submission #271575984 - Codeforces
忘记当时在写什么了。
[√]D
思考每行至多在 [0,4] 位置放置即可
Submission #271592202 - Codeforces
[√]E1
独立做出的第一个构造题。
考虑树根号分块然后撒点,将其确认到一条链上,然后询问根号次将其移动到点上分即可。
//自在飞花轻似梦,无边丝雨细如愁。
#include<bits/stdc++.h>
#define ll long long
#define N 200005
int cnt;
inline int request(int x){cnt ++ ;std::cout<<"? "<<x<<std::endl;scanf("%d",&x);return x;}
int T;
int n;
using std::vector;
vector<int>G[N];
vector<int>H[N];
int line[N];
int fa[N];
inline void dfs(int u,int ffa){
if(G[u].size() == 1 && G[u][0] == ffa){line[u] = 1;return;}
fa[u] = ffa;
for(auto v : G[u]){
if(v == ffa)continue;
dfs(v,u);
line[u] = std::max(line[v] + 1,line[u]);
}
}
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i)G[i].clear(),H[i].clear();
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);G[y].push_back(x);
}
dfs(1,0);
for(int i = 1;i <= n;++i)H[line[i]].push_back(i);
int bas = 40;
int rt = 0;int nrt = 0;
int leaf = H[1][0];
cnt = 0;
if(request(leaf)){std::cout<<"! "<<leaf<<std::endl;continue;}else{nrt = leaf;}
for(int deep = bas;deep < line[1];deep += bas){
for(auto u : H[deep]){
if(request(u)){rt = u;break;}
nrt = u;
}
if(rt)break;
}
if(!rt)rt = 1;
if(rt == 1){while(cnt < 300)request(nrt);std::cout<<"! 1"<<std::endl;continue;}
while(1){
request(nrt);
if(!request(rt)){std::cout<<"! "<<(fa[fa[rt]] ? fa[fa[rt]] : 1)<<std::endl;break;}
}
}
}
[x] E2
考虑先询问 \(\sqrt n\) 次叶子,然后将其限制在所有最大深度 \(> \sqrt n\) 的点中
直接 \(dfs\) 然后考虑找到老鼠在哪条链上然后进行二分,考虑到失败的次数不会超过 \(\sqrt n\) 次,因为你每次都至少删除了一条长为 \(\sqrt n\) 的链。
//自在飞花轻似梦,无边丝雨细如愁。
#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define bas 70
int cnt;
inline int request(int x){
std::cout<<"? "<<x<<std::endl;
int res;
scanf("%d",&res);
return res;
}
int T;
int n;
using std::vector;
vector<int>G[N];
vector<int>H[N];
int line[N];
int fa[N];
int deep[N];
inline void dfs(int u,int ffa){
deep[u] = deep[ffa] + 1;
fa[u] = ffa;
if(G[u].size() == 1 && G[u][0] == ffa){line[u] = 1;return;}
for(auto v : G[u]){
if(v == ffa)continue;
dfs(v,u);
line[u] = std::max(line[v] + 1,line[u]);
}
}
inline int find(int u,int ffa){
// std::cout<<u<<" "<<ffa<<"\n";
vector<int>L;L.clear();
for(auto v : G[u]){
if(v == ffa)continue;
if(line[v] > bas)L.push_back(v);
}
if(L.size() == 0)return u;
int nex = L[0];
for(int i = 1;i < L.size();++i){
int v = L[i];
if(request(v)){nex = v;break;}
}
return find(nex,u);
}
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d",&n);
for(int i = 1;i <= n;++i)G[i].clear(),H[i].clear();
for(int i = 1;i < n;++i){
int x,y;
scanf("%d%d",&x,&y);
G[x].push_back(y);G[y].push_back(x);
}
dfs(1,0);
for(int i = 1;i <= n;++i)H[line[i]].push_back(i);
int rt = 0;int nrt = 0;
int leaf = H[1][0];
cnt = 0;
// std::cout<<leaf<<"\n";
if(request(leaf)){std::cout<<"! "<<leaf<<std::endl;continue;}else{nrt = leaf;}
for(int i = 1;i <= bas - 1;++i)request(leaf);
int end = find(1,0);
// std::cout<<end<<"\n";
vector<int>L;L.clear();
int u = end;
while(u != 1){L.push_back(u);u = fa[u];}L.push_back(1);std::reverse(L.begin(),L.end());
int l = 1,r = deep[end];
#define mid ((l + r + 1) >> 1)
int ncnt = 0;
int ans = 0;
// for(auto v : L){std::cout<<v<<" ";}
// puts("");
while(l < r){
if(request(L[std::max(mid - 1 - ncnt,0)])){l = mid;}
else ncnt ++ ,r = mid - 1;
}
ans = L[std::max(l - 1 - ncnt,0)];
std::cout<<"! "<<ans<<std::endl;
}
}

浙公网安备 33010602011771号