24暑假赛训合集

本文同步自 24暑期赛训合集

谢谢,你关注的鸽子博主更新了。
上赛季末段没能忍住网瘾, 转生成 ACMer 了
和队友一起拿了块邀请赛金牌和省赛冠军,下半年区域赛不想拖后腿所以还是得努努力啊。
但是因为博主还要跑科研实验 以及 机器人比赛的事情,所以大概一天只能看几个题

唉,被自己菜晕,能不能来点作用,别浪费队友开出来的题。

下列列出的 √ 为自己想出来的,× 为看了题解。

个人训练赛记录,CF / AT 为主

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

[√] 不知名题

pi=(api12+bpi1+c)modm+pi12
p0=k 时,求 pT
k,T1e18
a,b,c,m5e5

看这个 m 显然就很能操作啊。
思考 pi2m 时显然最后范围也在 2m 中,m 不大显然可以处理值域上的步数信息。
pi>2m 时,理论上 log2m 步就进入范围了。
所以直接就倍增即可。

//明剑照霜,秋风走马
#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,否则答案为 n1

[√]D

gcd 是质因数次数取 min
lcm 是质因数次数取 max
死去的离散数学整除格正在攻击我
考虑实际上是把每一维质因数的次数排序。
因为 x+ygcd(x,y)+lcm(x,y),所以最后每一维的应该都是排序好的。
质因数分解然后硬来就行了。

[√]G

队友写的,这里看看题解做法。

考虑每一位的取模贡献 11xaxmod5

又因为 11xmod5=1

所以就是加起来取模 5 即可

[√]H

什么机器学习题

考虑卷积核上每一位的贡献在原矩阵上就是一个矩阵和,使用二维前缀和算出每一位贡献即可。

[√]J

麻将模拟题。

原本以为要判很多牌型,结果只有国士无双十三面和七对子。

队友看懂题之后就直接过了。

[√]K

考虑实际上等价于 (1,1)(2,n) 的方案数,实际上的答案就是 2(n1)

[√]L

考虑 K 非常小,实际上只要每次在一个门开门的时候跑一遍单源最短路,在关门的时候把答案去掉即可。

[√]F

队友上来就toptree直接写了。

非常的牛。

我只会那个线段树分治加并查集的 2log 做法,但是鉴于我队友看起来是数据结构机器人,感觉很难有他不会的数据结构题,我就不写了。

[√]I

考虑到覆盖边其实是无所谓的条件。

转化成覆盖点。

然后考虑枚举所有的外接圆,一个圆可以由 3 个在圆上的点确定,或者 2 个直径上的点确定,直接 O(n2+n3) 枚举即可。

然后考虑一个圆内能覆盖多少个点,然后统计答案即可。

[x]E

给定一个序列,求找出两个不同的子序列,要求两个子序列和相同,给出方案。

非常苦手,队友上来说应该是什么随机题,非常神秘。

首先知道一个长度 n 的序列,其子序列数为 2n

其值域 S[0,An],viA

那在 29n 时 存在 An2n

这说明如果一个序列长度大于等于 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(3min(15,n2)log(3min(15,n2)))

有点通过不了。

这里其实有很多种处理方法,后面会提到,先介绍题目所说的三个序列归并的方法。

我们考虑我们在搜索状压的时候,做的其实是二元状态 (s,d) 的变更

我们从前 i1 个元素的方案加入第 i 个元素,然后考虑更新方案

实际上是

(s,d)(s×3,d)

(s,d)(s×3+1,d+ai)

(s,d)(s×3+2,dai)

分别对应上述三种加入方案

那么我们只要在加入到第i1元素,就把所有的 3i1 的二元状态按照 d 排好序,那么我们加入第 i 个元素的时候,实际上是对上面三种序列归并排序即可。

你可以直接把三个序列先分别求出来再排序,也可以直接维护三个头指针,三指针移动的把三种对应的最小的先放进 3i 个状态对应的答案序列里,然后加对应指针向后挪一位即可。

然后我们得到了前十五个的状态,和后十五的状态,由于均有序,我们直接就双指针查有没有相反数即可。

//樱花落尽阶前月,象床愁倚薰笼
#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 段,每段代价为 (i=1nxi×x)x2,求最小代价

容易推导到代价实际为 (1+xi)2

如果没有 m 段的代价实际上,我们可以直接使用斜率优化完成这个题

ji:fi=fj+(SiSj+1)2fi=fj+Si22×Si×(Sj1)+(Sj1)2

2×Si×(Sj1)+fiSi2=fj+(Sj1)2

即存在一条斜率为 2×Si 的直线通过 (Sj1,fj+(Sj1)2)

由于要 fi 最小,则是求最小截距,那即为维护下凸包单调队列即可

接着考虑如何强制选 m

这是一个经典的条件,我们考虑 gk 表示分了 k 段的答案,其显然是一个上凸包

我们考虑二分凸包斜率,找到 m 所在的凸包位置即可。

然后这个斜率我们以 (k,lk) 的形式表示,其实即为每次转移附带了一个 l 的代价

然后我们返回可以分的段数

这里需要注意可能同一斜率上有多个点,这里应该返回最小的段数/最大的段数,后面二分自洽即可,最后的答案减去 m×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

fi,ji 到父亲的边不断,其父亲到 i 子树内最长路径为 j 的方案数

特殊的 fi,0 表示断开 i 和父亲的边

考虑如何统计答案,实际上就是任两子树内最长路径相加不大于 k 的方案数

考虑如果显然子树内不能存在两条大于 k2 的链,先对所有子树前缀统计,然后枚举大于 k2 的在哪个子树,然后其余的所有子树限制为 kl,l 为枚举的最长链。

若不存在大于 k2 正常前缀算答案即可。

//樱花落尽阶前月,象床愁倚薰笼
#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

构造一个 (i|kai)|k 的数列

直接输出 1,...,n 即可

[√]B

给定两矩阵 A,B,每次可以选择一个矩形的四个角,左上角和右下角加 1,右上角左下角加 2,所有元素对 3 取模

考虑到实际上每行每列的对 3 取模不变,容易证明其为充要条件。

充分条件可以考虑每行调整即可。

[√]C

给定 n 元序列 A,B,C, 每个序列和均为 sum , 求把 A,B,C 取三个下标不交的子段,要求每个子段和大于 13sum

考虑枚举中间那段在哪,然后双指针即可,考虑显然是前一段为 [1,l1],后一段为 [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,每次可以在 AB 中选择两个下标差相同的(Ai,Aj),(Bp,Bq),将 Ai,Aj 值交换,Bp,Bq 交换

问能否最后相同。

显然权值集合要相同

然后跨越 d 的交换可以拆分成若干相邻交换,相邻交换逆序对奇偶改变

所以充要条件是 A,B 的逆序对奇偶相同

考虑充分条件构造,直接把一个调整到 1,...,n,然后调整另外一边,此时另外一遍调整一定是偶数次,在调整好的一直选中相邻两个交换即可。

[√]F

给定一个序列 A,其中一个区间的代价为 vall,r=minlx,yrAXAy

问区间代价第 k 个为多少。

先二分

然后考虑如何计算 mid 的答案

我们考虑枚举子点对的右端点 r,我们找到离他最近的点 l, 满足 ArAlmid,那么包含其的区间数量为 [1,l]×[r,n]

考虑如何统计所有的数量,你显然可以 2log 来矩阵并,这样你就爆爆了

我们考虑可以扫描线,我们双指针维护前缀最大的 l,这个点的最近的点对为 l,那么新增的答案为 max(ll,0)×(nr+1)

只有 l>l 有贡献,那么我们直接维护前缀指针 l ,每次尝试暴力向右移动即可,判断 [l,r] 中是否右AiArmid

使用 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

我们只先计算先手球的期望。

首先思考到所有的特殊球和普通球的贡献是分开的

然后普通球的贡献是好计算的,c+12iCaic

然后思考特殊球的贡献,我当时以为是分成两段,但是后来看来是用 c 个普通球,分成了 c+1 段,每人按顺序取,所以特殊球的期望为 c+22iSaic+1

The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest

[√]A

考虑根号操作只有 x 个数,若平方完再根号,对于不同的数没有任何贡献。

那么考虑先对 a 取下取整根号操作得到 b,若 b2=a 则无贡献,否则有剩余贡献即可。

[√]D

队友发现答案一直是lose,输出 n 个 lose 即可。

[√]E

要求 B=popcount(A)+popcount(B)mod2k

考虑反过来 B 固定时,popcount(A) 唯一即可

[√]M

考虑如何不重复的计这个,考虑枚举中间按两个点,然后尝试枚举下面矩形点对,然后正常统计即可。队友写了各种叉积判断避免了精度问题。

[√]H

给定 n 个点的树,m 个点对,可选择一个点为根,要求所有 2m 个点到它们对应的 LCA 最大距离最小。

考虑二分答案,对于一个点对的方案形为 k 极祖先的子树内/外。

考虑其为 dfs 序的 O(1) 段区间,查询是否交集为空即可。

[√]I

考虑最后一个完整排列的 fi

fi=j=1kfij×gj

其中 gj 为前缀 j 均不为子排列的方案数。

这个考虑容斥即可

gi=i!j=1i1j!×gij

[√]K

考虑一层层的从右到左的放置 ri

下一层查询在上一层的左端点和其离的最近的 ri,判断若 l=l,则需要 r=r1 否则 r=r

[√]L

考虑先取所有最小单位 () ,然后依次打括号即可

[√]F

pq 为有限小数,统计 p

q 的质因子 pi ,其次幂小于等于 p 的幂次

考虑直接 dfs 即可。

[√]G

考虑对出现次数根号分治即可

含有出现次数大的那边离线下来做,小的直接暴力。

[√] B

实际上每一层都实际上依赖于第二层

假设二级一开始是工作状态,则一级一开始是不工作状态,三级一开始是辅助供能状态。

注意到三级的供能站想要转为供能状态,则需要二级处于不工作状 态;一级供能站想要转为供能状态,则需要二级处于辅助供能状态。

考虑将第二层拆成 供能转不工作 不工作转辅助

然后将有需求的向 供能转不工作 连边

一级功能向两者同时连,均为无穷,最小割割不掉即可。

复杂度正确性存疑。

[×] C

思考到:

你第一次改成了什么,最后就是什么

两个方向的答案是一样的

那么考虑如何快速算答案,双指针预处理出 fi,0/1 为区间 [i,(i+k1)modn] 变成0/1 的下一步操作该到哪里。

然后枚举第一步在哪,然后倍增即可。

代码暂无

Codeforces Round 958 (Div. 2)

[√]A

显然每次都扩展 k1 个 1

[√]B

考虑可以先把所有的长度大于 10 串先变成长度为 1 的。

接着检查是否 1 为剩下串的众数即可。

[√]C

考虑到要求相邻 or 的结果为 n,那么即所有数的二进制下 1 集合为 n 的子集。

又要求严格递增,那么考虑依次从低位去掉 1 即可。

[√] D

又是一场想完没写完的做法。

场上一直写的是线性做法,我说怎么大家过的都这么快。

首先题意要求每次删除若干不连通点,最后求每个回合中的点权和。

考虑我们在第 i 回合删除了点 x,他对最后答案的贡献为 i×ax

那么我们将题目转化为我们需要为每个点赋上一个权 bi,要求相邻点的赋权不相同,最小化 bi×ai

我们容易写出 fi,j 表示 i 点赋权 j,子树内 bx×ax 的最小和。

容易写出转移式即 fi,j=vsoniminkjfv,k+j×ai

我们知道 bin ,但是 n 只是我们随手估的一个不太好的上界,在官方题解中给出了一个更好的上界 bilogn,于是可以在 O(nlog2n) 或者 O(nlogn) 中解决这个问题。

但是我们有完全不需要基于上界的做法。

我们思考 minkjfv,k 这个函数,实际上的值是一个分类函数。

我们设 sfv,s=minfv,k

同时设 s,fv,s=minksfv,s

v 的最小值和次小值点。

那么 (1)minkjfv,k={fv,sjsfv,sj=s

于是我们发现实际上 vsoniminkjfv,k 只有至多 |soni|+1 种取值,即我们取任一子树的最小值对应的点,和不取任何一个所对应的值,这里我们称儿子的最小值的点的集合为 S

那么我们发现我们实际上并不需要维护所有点,我们只需要维护一个点的最小值和次小值位置即可。

那么我们接着来考虑后面这个 j×ai 函数,其为一个递增函数。我们考虑我们取任一子树的最小值对应的点,j 是固定无法修改的。但是对 jS ,我们只需要取 mex(S)mex(S+mex(S))

|soni|+2 种答案中就能取到最小值和次小值。

这里我们采取了直接暴力一点的做法我直接取了 S,S+1,S+2 三种情况,在 3|soni| 种情况取最小值和次小值,容易证明 mex(S)+mex(S+mex(S)){S+1}{S+2}

同时我在代码里使用 map,可以使用 数组 或 Hash 表等其他线性映射结构来替代掉这个 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

则在 k1 轮,我们考虑 |S|=nk+1,|AS|=k1

则依据鸽巢原理定 xAS,yS,xmodk=ymodk

那么知道这个那直接连边即可。

[√]E

很好的诈骗题,让我头脑旋转

考虑每颗树可以得到 [0,s] 的任意树,实际上就是所有的值 or 起来最大即可。

[x]F

回忆欧拉回路的条件,每个点的度数都为偶数。

考虑转成奇偶,那么实际上就是给定了一个初态 S,判断若干 (x,y),每次操作形如取反 Sx,Sy

实际上就是异或了 0...1...010...0

我们从线性基的角度来考虑,我们首先找到若干不交线性基 B

但是需要输出方案就是一个比较困难的事情,由于我们无法和正常线性基一样按位处理。

但是我们发现这若干基里 (x,y) 不成环,也就是在点的控制关系中有拓扑关系,我们从拓扑序考虑每个点是否调整即可。

实际上这个就是题解里所说的从 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 为主

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

[√] 不知名题

pi=(api12+bpi1+c)modm+pi12
p0=k 时,求 pT
k,T1e18
a,b,c,m5e5

看这个 m 显然就很能操作啊。
思考 pi2m 时显然最后范围也在 2m 中,m 不大显然可以处理值域上的步数信息。
pi>2m 时,理论上 log2m 步就进入范围了。
所以直接就倍增即可。

//明剑照霜,秋风走马
#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,否则答案为 n1

[√]D

gcd 是质因数次数取 min
lcm 是质因数次数取 max
死去的离散数学整除格正在攻击我
考虑实际上是把每一维质因数的次数排序。
因为 x+ygcd(x,y)+lcm(x,y),所以最后每一维的应该都是排序好的。
质因数分解然后硬来就行了。

[√]G

队友写的,这里看看题解做法。

考虑每一位的取模贡献 11xaxmod5

又因为 11xmod5=1

所以就是加起来取模 5 即可

[√]H

什么机器学习题

考虑卷积核上每一位的贡献在原矩阵上就是一个矩阵和,使用二维前缀和算出每一位贡献即可。

[√]J

麻将模拟题。

原本以为要判很多牌型,结果只有国士无双十三面和七对子。

队友看懂题之后就直接过了。

[√]K

考虑实际上等价于 (1,1)(2,n) 的方案数,实际上的答案就是 2n1

[√]L

考虑 K 非常小,实际上只要每次在一个门开门的时候跑一遍单源最短路,在关门的时候把答案去掉即可。

[√]F

队友上来就toptree直接写了。

非常的牛。

我只会那个线段树分治加并查集的 2log 做法,但是鉴于我队友看起来是数据结构机器人,感觉很难有他不会的数据结构题,我就不写了。

[√]I

考虑到覆盖边其实是无所谓的条件。

转化成覆盖点。

然后考虑枚举所有的外接圆,一个圆可以由 3 个在圆上的点确定,或者 2 个直径上的点确定,直接 O(n2+n3) 枚举即可。

然后考虑一个圆内能覆盖多少个点,然后统计答案即可。

[x]E

给定一个序列,求找出两个不同的子序列,要求两个子序列和相同,给出方案。

非常苦手,队友上来说应该是什么随机题,非常神秘。

首先知道一个长度 n 的序列,其子序列数为 2n

其值域 S[0,An],viA

那在 29n 时 存在 An2n

这说明如果一个序列长度大于等于 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(3min(15,n2)log(3min(15,n2)))

有点通过不了。

这里其实有很多种处理方法,后面会提到,先介绍题目所说的三个序列归并的方法。

我们考虑我们在搜索状压的时候,做的其实是二元状态 (s,d) 的变更

我们从前 i1 个元素的方案加入第 i 个元素,然后考虑更新方案

实际上是

(s,d)(s×3,d)

(s,d)(s×3+1,d+ai)

(s,d)(s×3+2,dai)

分别对应上述三种加入方案

那么我们只要在加入到第i1元素,就把所有的 3i1 的二元状态按照 d 排好序,那么我们加入第 i 个元素的时候,实际上是对上面三种序列归并排序即可。

你可以直接把三个序列先分别求出来再排序,也可以直接维护三个头指针,三指针移动的把三种对应的最小的先放进 3i 个状态对应的答案序列里,然后加对应指针向后挪一位即可。

然后我们得到了前十五个的状态,和后十五的状态,由于均有序,我们直接就双指针查有没有相反数即可。

//樱花落尽阶前月,象床愁倚薰笼
#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 段,每段代价为 (i=1nxi×x)x2,求最小代价

容易推导到代价实际为 (1+xi)2

如果没有 m 段的代价实际上,我们可以直接使用斜率优化完成这个题

ji:fi=fj+(SiSj+1)2fi=fj+Si22×Si×(Sj1)+(Sj1)2

2×Si×(Sj1)+fiSi2=fj+(Sj1)2

即存在一条斜率为 2×Si 的直线通过 (Sj1,fj+(Sj1)2)

由于要 fi 最小,则是求最小截距,那即为维护下凸包单调队列即可

接着考虑如何强制选 m

这是一个经典的条件,我们考虑 gk 表示分了 k 段的答案,其显然是一个上凸包

我们考虑二分凸包斜率,找到 m 所在的凸包位置即可。

然后这个斜率我们以 (k,lk) 的形式表示,其实即为每次转移附带了一个 l 的代价

然后我们返回可以分的段数

这里需要注意可能同一斜率上有多个点,这里应该返回最小的段数/最大的段数,后面二分自洽即可,最后的答案减去 m×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

fi,ji 到父亲的边不断,其父亲到 i 子树内最长路径为 j 的方案数

特殊的 fi,0 表示断开 i 和父亲的边

考虑如何统计答案,实际上就是任两子树内最长路径相加不大于 k 的方案数

考虑如果显然子树内不能存在两条大于 k2 的链,先对所有子树前缀统计,然后枚举大于 k2 的在哪个子树,然后其余的所有子树限制为 kl,l 为枚举的最长链。

若不存在大于 k2 正常前缀算答案即可。

//樱花落尽阶前月,象床愁倚薰笼
#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

构造一个 (i|kai)|k 的数列

直接输出 1,...,n 即可

[√]B

给定两矩阵 A,B,每次可以选择一个矩形的四个角,左上角和右下角加 1,右上角左下角加 2,所有元素对 3 取模

考虑到实际上每行每列的对 3 取模不变,容易证明其为充要条件。

充分条件可以考虑每行调整即可。

[√]C

给定 n 元序列 A,B,C, 每个序列和均为 sum , 求把 A,B,C 取三个下标不交的子段,要求每个子段和大于 13sum

考虑枚举中间那段在哪,然后双指针即可,考虑显然是前一段为 [1,l1],后一段为 [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,每次可以在 AB 中选择两个下标差相同的(Ai,Aj),(Bp,Bq),将 Ai,Aj 值交换,Bp,Bq 交换

问能否最后相同。

显然权值集合要相同

然后跨越 d 的交换可以拆分成若干相邻交换,相邻交换逆序对奇偶改变

所以充要条件是 A,B 的逆序对奇偶相同

考虑充分条件构造,直接把一个调整到 1,...,n,然后调整另外一边,此时另外一遍调整一定是偶数次,在调整好的一直选中相邻两个交换即可。

[√]F

给定一个序列 A,其中一个区间的代价为 vall,r=minlx,yrAXAy

问区间代价第 k 个为多少。

先二分

然后考虑如何计算 mid 的答案

我们考虑枚举子点对的右端点 r,我们找到离他最近的点 l, 满足 ArAlmid,那么包含其的区间数量为 [1,l]×[r,n]

考虑如何统计所有的数量,你显然可以 2log 来矩阵并,这样你就爆爆了

我们考虑可以扫描线,我们双指针维护前缀最大的 l,这个点的最近的点对为 l,那么新增的答案为 max(ll,0)×(nr+1)

只有 l>l 有贡献,那么我们直接维护前缀指针 l ,每次尝试暴力向右移动即可,判断 [l,r] 中是否右AiArmid

使用 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

我们只先计算先手球的期望。

首先思考到所有的特殊球和普通球的贡献是分开的

然后普通球的贡献是好计算的,c+12iCaic

然后思考特殊球的贡献,我当时以为是分成两段,但是后来看来是用 c 个普通球,分成了 c+1 段,每人按顺序取,所以特殊球的期望为 c+22iSaic+1

The 2024 CCPC National Invitational Contest (Northeast), The 18th Northeast Collegiate Programming Contest

[√]A

考虑根号操作只有 x 个数,若平方完再根号,对于不同的数没有任何贡献。

那么考虑先对 a 取下取整根号操作得到 b,若 b2=a 则无贡献,否则有剩余贡献即可。

[√]D

队友发现答案一直是lose,输出 n 个 lose 即可。

[√]E

要求 B=popcount(A)+popcount(B)mod2k

考虑反过来 B 固定时,popcount(A) 唯一即可

[√]M

考虑如何不重复的计这个,考虑枚举中间按两个点,然后尝试枚举下面矩形点对,然后正常统计即可。队友写了各种叉积判断避免了精度问题。

[√]H

给定 n 个点的树,m 个点对,可选择一个点为根,要求所有 2m 个点到它们对应的 LCA 最大距离最小。

考虑二分答案,对于一个点对的方案形为 k 极祖先的子树内/外。

考虑其为 dfs 序的 O(1) 段区间,查询是否交集为空即可。

[√]I

考虑最后一个完整排列的 fi

fi=j=1kfij×gj

其中 gj 为前缀 j 均不为子排列的方案数。

这个考虑容斥即可

gi=i!j=1i1j!×gij

[√]K

考虑一层层的从右到左的放置 ri

下一层查询在上一层的左端点和其离的最近的 ri,判断若 l=l,则需要 r=r1 否则 r=r

[√]L

考虑先取所有最小单位 () ,然后依次打括号即可

[√]F

pq 为有限小数,统计 p

q 的质因子 pi ,其次幂小于等于 p 的幂次

考虑直接 dfs 即可。

[√]G

考虑对出现次数根号分治即可

含有出现次数大的那边离线下来做,小的直接暴力。

[√] B

实际上每一层都实际上依赖于第二层

假设二级一开始是工作状态,则一级一开始是不工作状态,三级一开始是辅助供能状态。

注意到三级的供能站想要转为供能状态,则需要二级处于不工作状 态;一级供能站想要转为供能状态,则需要二级处于辅助供能状态。

考虑将第二层拆成 供能转不工作 不工作转辅助

然后将有需求的向 供能转不工作 连边

一级功能向两者同时连,均为无穷,最小割割不掉即可。

复杂度正确性存疑。

[×] C

思考到:

你第一次改成了什么,最后就是什么

两个方向的答案是一样的

那么考虑如何快速算答案,双指针预处理出 fi,0/1 为区间 [i,(i+k1)modn] 变成0/1 的下一步操作该到哪里。

然后枚举第一步在哪,然后倍增即可。

代码暂无

Codeforces Round 958 (Div. 2)

[√]A

显然每次都扩展 k1 个 1

[√]B

考虑可以先把所有的长度大于 10 串先变成长度为 1 的。

接着检查是否 1 为剩下串的众数即可。

[√]C

考虑到要求相邻 or 的结果为 n,那么即所有数的二进制下 1 集合为 n 的子集。

又要求严格递增,那么考虑依次从低位去掉 1 即可。

[√] D

又是一场想完没写完的做法。

场上一直写的是线性做法,我说怎么大家过的都这么快。

首先题意要求每次删除若干不连通点,最后求每个回合中的点权和。

考虑我们在第 i 回合删除了点 x,他对最后答案的贡献为 i×ax

那么我们将题目转化为我们需要为每个点赋上一个权 bi,要求相邻点的赋权不相同,最小化 bi×ai

我们容易写出 fi,j 表示 i 点赋权 j,子树内 bx×ax 的最小和。

容易写出转移式即 fi,j=vsoniminkjfv,k+j×ai

我们知道 bin ,但是 n 只是我们随手估的一个不太好的上界,在官方题解中给出了一个更好的上界 bilogn,于是可以在 O(nlog2n) 或者 O(nlogn) 中解决这个问题。

但是我们有完全不需要基于上界的做法。

我们思考 minkjfv,k 这个函数,实际上的值是一个分类函数。

我们设 sfv,s=minfv,k

同时设 s,fv,s=minksfv,s

v 的最小值和次小值点。

那么 (2)minkjfv,k={fv,sjsfv,sj=s

于是我们发现实际上 vsoniminkjfv,k 只有至多 |soni|+1 种取值,即我们取任一子树的最小值对应的点,和不取任何一个所对应的值,这里我们称儿子的最小值的点的集合为 S

那么我们发现我们实际上并不需要维护所有点,我们只需要维护一个点的最小值和次小值位置即可。

那么我们接着来考虑后面这个 j×ai 函数,其为一个递增函数。我们考虑我们取任一子树的最小值对应的点,j 是固定无法修改的。但是对 jS ,我们只需要取 mex(S)mex(S+mex(S))

|soni|+2 种答案中就能取到最小值和次小值。

这里我们采取了直接暴力一点的做法我直接取了 S,S+1,S+2 三种情况,在 3|soni| 种情况取最小值和次小值,容易证明 mex(S)+mex(S+mex(S)){S+1}{S+2}

同时我在代码里使用 map,可以使用 数组 或 Hash 表等其他线性映射结构来替代掉这个 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

则在 k1 轮,我们考虑 |S|=nk+1,|AS|=k1

则依据鸽巢原理定 xAS,yS,xmodk=ymodk

那么知道这个那直接连边即可。

[√]E

很好的诈骗题,让我头脑旋转

考虑每颗树可以得到 [0,s] 的任意树,实际上就是所有的值 or 起来最大即可。

[x]F

回忆欧拉回路的条件,每个点的度数都为偶数。

考虑转成奇偶,那么实际上就是给定了一个初态 S,判断若干 (x,y),每次操作形如取反 Sx,Sy

实际上就是异或了 0...1...010...0

我们从线性基的角度来考虑,我们首先找到若干不交线性基 B

但是需要输出方案就是一个比较困难的事情,由于我们无法和正常线性基一样按位处理。

但是我们发现这若干基里 (x,y) 不成环,也就是在点的控制关系中有拓扑关系,我们从拓扑序考虑每个点是否调整即可。

实际上这个就是题解里所说的从 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 ,则这一位进位至多 n+k2

我们可以考虑 knn+k2n

则这个地方我们直接暴力设计 fi,j 为第 i 位进位 j 的方案即可。

所以在设计 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

考虑先询问 n 次叶子,然后将其限制在所有最大深度 >n 的点中

直接 dfs 然后考虑找到老鼠在哪条链上然后进行二分,考虑到失败的次数不会超过 n 次,因为你每次都至少删除了一条长为 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;
	}
}

本文作者:fhq_treap

本文链接:https://www.cnblogs.com/dixiao/p/18279702

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   fhq_treap  阅读(109)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起