QBLT2023NOIP实战 比赛汇总<1>

Day1

\(80+0+0+0=80\)

自我评价:C

\(排名44/58(总人数)\)

A 大嘴乌鸦(沁志)

题意

大嘴乌鸦有 \(n\) 个水瓶,第 \(i\) 个水瓶的重量为 \(a_i\)
大嘴乌鸦要喝水,所以他找来了一堆石子,这些石子的重量和为 \(k\)
乌鸦发现,如果一个区间的水瓶的重量的异或和是 \(k\) 的因子,则这个区间是一个喝水区间。
乌鸦想知道,有多少个不同的喝水区间。
数据范围:\(n\le 10^5,1\le a_i,k \le n\)

输入格式
第一行两个数字 \(n\) 表示水瓶个数,以及石子的重量和 \(k\)
第二行 \(n\) 个正整数,第 \(i\) 个数表示 \(a_i\)

输出格式
一行一个整数表示有多少不同的喝水区间。

样例输入
5 3
1 2 3 4 5

样例输出
6

考场思路

大暴力。
枚举左端点 \(l\),右端点 \(r\)\(r\) 的范围是 \(1\)\(n\)。令 \(sum\)\(l\)\(r\) 的异或和,同前缀和,显然可以分别求出 \([l,l \sim n]\)\(sum\),复杂度 \(O(n^2)\)

代码(\(80\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define here printf("here\n");
int n,K;
int a[100001];
int yihuo,ans;
int main()
{
	cin>>n>>K;
	int i;
	for(i=1;i<=n;i++)
		cin>>a[i];
	yihuo = a[1];
	for(i=2;i<=n;i++)
		yihuo ^= a[i];
	int j,k;
	for(i=1;i<=n;i++)
	{
		for(j=i;j<=n;j++)
		{
			int yh = a[i];
			for(k=i+1;k<=j;k++)
			{
				yh ^= a[k];
			}
			if(yh)
			{
				if(K%yh==0)
				{
					ans++;
				}
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

正解

先处理出 \(k\) 的因子,放入 \(b\)。然后前缀异或一下 \(a\) 数组(就是前缀和改个符号)。开一个桶 \(vis\),统计 \(a_i\) 出现的次数。并依次枚举左端点和因子,看看桶内有没有 \(a_i \times b_j\),如果有,那么答案就加上 \(vis_{a_i \times b_j}\)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int a[1000001],n,sum[1000001];
int k;
int vis[1000001],cnt;
vector<int> b;
int ans;
int main()
{
	cin>>n>>k;
	int i;
	for(i=1;i<=n;i++)
		cin>>a[i];
	for(i=1;i<=n;i++)
		sum[i] = sum[i-1]^a[i];
	for(i=1;i<=k;i++)
		if(k%i==0)
			b.push_back(i);
	for(i=1;i<=n;i++)
	{
		vis[sum[i-1]]++;
		for(auto j:b)
		{
			ans += vis[sum[i]^j];
		}
	}
	cout<<ans<<endl;
	return 0;
}

B 艾莎

题意

艾莎给你一个长度为 \(n\) 的序列 \(a\)\(m\) 次操作,共两种操作类型:
1. 给定 \(l,r,x\),将 \(a_l \cdots a_r\) 加上 \(x\)
2. 给定 \(l,r\),查询 \(\max\limits_{l\le L < R \le r} \dfrac{\sum\limits_{i=L}^R a_i}{R-L+1}\)
数据范围:\(1 \le n,m \le 10^6,|a_i|,|x| \le 10^3\),保证都是整数。对于操作 \(2\),保证不存在 \(l=r\) 的情况。

输入格式
第一行:\(n,m\)
第二行:\(a\) 序列;
接下来 \(m\) 行,每行 \(1,l,r,x\)\(2,l,r\) 表示一个操作。

输出格式
对于每个操作 \(2\),输出一行,包含一个最简分数(形如 a/b-a/b0/1\(a,b\) 是互质的)。

样例输入
5 8
-7 -8 -1 5 8
1 4 5 -3
1 2 3 7
1 5 5 3
1 1 4 1
2 4 5
1 3 4 -1
1 1 2 7
2 4 5

样例输出
11/2
5/1

考场思路

操作 \(2\) 的人话:求 \([l,r]\) 中最大的子段的平均值。
线段树 \(+\) 暴力,但爆零。
代码(\(0\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ls (p<<1)
#define rs (p<<1|1)
#define int long long
int n;
const int N = 100005;
int a[N];
struct point
{
	int l,r;
	int sum,lazy;
}t[N*4];
void push_up(int p)
{
	t[p].sum = t[ls].sum+t[rs].sum;
}
void push_down(int p)
{
	if(t[p].lazy)
	{
		t[ls].sum += t[p].lazy*(t[ls].r-t[ls].l+1);
		t[rs].sum += t[p].lazy*(t[rs].r-t[rs].l+1);
		t[ls].lazy += t[p].lazy;
		t[rs].lazy += t[p].lazy;
		t[p].lazy = 0;
		return;
	}
}
void build(int p,int l,int r)
{
	t[p].l = l;
	t[p].r = r;
	if(l==r)
	{
		t[p].sum = a[l];
		return;
	}
	int mid = (t[p].l+t[p].r)/2;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(p);
}
void add(int p,int l,int r,int d)
{
	if(t[p].l>=l&&t[p].r<=r)
	{
		t[p].sum += (t[p].r-t[p].l+1)*d;
		t[p].lazy += d;
		return;
	}
	push_down(p);
	int mid = (t[p].l+t[p].r)/2;
	if(l<=mid)
		add(ls,l,r,d);
	if(mid<r)
		add(rs,l,r,d);
	push_up(p);
}
int get(int p,int l,int r)
{
	if(l<=t[p].l&&t[p].r<=r)
		return t[p].sum;
	push_down(p);
	int ans = 0;
	int mid = (t[p].l+t[p].r)/2;
	if(l<=mid)
		ans += get(ls,l,r);
	if(mid<r)
		ans += get(rs,l,r);
	return ans;
}
int gcd(int a,int b)
{
	return b?gcd(b,a%b):a;
}
main()
{
	int t;
	cin>>n>>t;
	int i,j;
	for(i=1;i<=n;i++)
		cin>>a[i];
	build(1,1,n);
	while(t--)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int l,r,k;
			cin>>l>>r>>k;
			add(1,l,r,k); 
		}
		else
		{
			int l,r;
			cin>>l>>r;
			double ma = -114514.0;
			int fmm,fzz;
			for(i=l;i<r;i++)
			{
				for(j=i+1;j<=r;j++)
				{
					int fm = get(1,i,j);
					int fz = j-i+1;
					double fs = fm*1.0/fz;
					if(fs>ma)
						ma = fs,fmm = fm,fzz = fz;
				}
			}
			int g = gcd(fmm,fzz);
			if(fzz>0&&fmm<0)
				fzz = -fzz,fmm = -fmm;
			cout<<fmm/g<<'/'<<fzz/g<<endl;
		}
	}
	return 0;
}

正解

先来挖掘一个很奇妙的性质:

  • 考虑任意一个长度 \(\ge 4\) 的区间,可以被划分成两个长度 \(\ge 2\) 的区间。而根据常识,这两个区间的平均值一定有一个不比原区间的平均值小,故答案区间 \(\le 3\)

然后用线段树维护每个长度为 \(2\) 的区间和长度为 \(3\) 的区间的最大值即可。时间复杂度 \(O(n\log n)\)

证明:
考虑取得 \(\max\)\(L,R\),取 \(R-L+1\) 最小的那组,以及序列 \(a\) 的前缀和 \(s_i = \sum\limits_{j=1}^i a_j\)。假设 \(R-L+1>3\)
定义 \(F(L,R)=\dfrac{s_R-s_L}{R-L}\),则:

\[F(L-1,R)>\max(F(L,R),F(L-1,R-1)) \]

由斜率的性质 \(F(L-1,R)<\min(F(L-1,L),F(R-1,R))\),构造连续函数 \(f(x)\) 满足 \(f(i) = s\),在 \(i-1 <x <i\)\(f(x)\) 为斜率 \(F(i-1,i)\) 的一次函数,则由零点存在定理等可以找出 \(f(x)\) 在区间 \((L,R-1)\) 上与经过 \((L-1,s_{L-1})\)\((R,s_R)\) 的一次函数的交点,取最左的交点下取整为 \(L-1<M,M+1<R\),于是 \(F(L-1,M)\ge F(L-1,R)\)\(F(M+1,R)\ge F(L-1,R)\),矛盾。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
int n,m;
int a[1000005]; 
struct Tree{
	int l,r,len;
	ll tar,w,s2=-1e18,L,R;
	ll s3=-1e18,LL=-1e18,RR=-1e18;
}t[4000005];

void update(int p){
	t[p].w=t[p<<1].w+t[p<<1|1].w;
	t[p].L=t[p<<1].L,t[p].R=t[p<<1|1].R;
	if(t[p<<1].len>=2) t[p].LL=t[p<<1].LL;
	else t[p].LL=t[p<<1].w+t[p<<1|1].L;
	if(t[p<<1|1].len>=2) t[p].RR=t[p<<1|1].RR;
	else t[p].RR=t[p<<1|1].w+t[p<<1].R;
	t[p].s2=max(max(t[p<<1].s2,t[p<<1|1].s2),t[p<<1].R+t[p<<1|1].L);
	t[p].s3=max(max(t[p<<1].s3,t[p<<1|1].s3),max(t[p<<1].RR+t[p<<1|1].L,t[p<<1].R+t[p<<1|1].LL));
}

void build(int p,int l,int r){
	t[p].l=l,t[p].r=r,t[p].len=t[p].r-t[p].l+1;
	if(l==r){
		t[p].w=a[l];
		t[p].L=a[l],t[p].R=a[l];
		return;
	}
	int mid=((ll)l+(ll)r)>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	update(p);
}

void push(int p){
	if(t[p].tar){
		t[p<<1].w+=(t[p<<1].r-t[p<<1].l+1)*t[p].tar;
		t[p<<1|1].w+=(t[p<<1|1].r-t[p<<1|1].l+1)*t[p].tar;
		t[p<<1].L+=t[p].tar,t[p<<1].R+=t[p].tar;
		t[p<<1|1].L+=t[p].tar,t[p<<1|1].R+=t[p].tar;
		if(t[p<<1].len>=2) t[p<<1].s2+=2*t[p].tar,t[p<<1].LL+=2*t[p].tar,t[p<<1].RR+=2*t[p].tar;
		if(t[p<<1|1].len>=2) t[p<<1|1].s2+=2*t[p].tar,t[p<<1|1].LL+=2*t[p].tar,t[p<<1|1].RR+=2*t[p].tar; 
		if(t[p<<1].len>=3) t[p<<1].s3+=3*t[p].tar;
		if(t[p<<1|1].len>=3) t[p<<1|1].s3+=3*t[p].tar;
		t[p<<1].tar+=t[p].tar,t[p<<1|1].tar+=t[p].tar;
		t[p].tar=0;
	}
}

void add(int p,int l,int r,int x){
	if(l<=t[p].l&&t[p].r<=r){
		t[p].tar+=x;
		t[p].L+=x,t[p].R+=x; 
		if(t[p].len>=2) t[p].s2+=2*x,t[p].LL+=2*x,t[p].RR+=2*x;
		if(t[p].len>=3) t[p].s3+=3*x;
		t[p].w+=t[p].len*x;
		return;
	}
	push(p);
	int mid=(t[p].r+t[p].l)>>1;
	if(l<=mid) add(p<<1,l,r,x);
	if(mid<r) add(p<<1|1,l,r,x);
	update(p);
}

Tree query2(int p,int l,int r){
	if(l<=t[p].l&&t[p].r<=r){
		return t[p];
	}
	push(p);
	int mid=((ll)t[p].r+(ll)t[p].l)>>1;
	Tree left,right,now;
	if(r<=mid){
		return query2(p<<1,l,r);
	}
	if(l>mid){
		return query2(p<<1|1,l,r);
	}
	left=query2(p<<1,l,r),right=query2(p<<1|1,l,r);
	now.L=left.L,now.R=right.R;
	now.s2=max(max(left.s2,right.s2),left.R+right.L);
	now.w=left.w+right.w;
	return now;
}

Tree query3(int p,int l,int r){
	if(l<=t[p].l&&t[p].r<=r){
		return t[p];
	}
	push(p);
	int mid=((ll)t[p].r+(ll)t[p].l)>>1;
	Tree left,right,now;
	if(r<=mid){
		return query3(p<<1,l,r);
	}
	if(l>mid){
		return query3(p<<1|1,l,r);
	}
	left=query3(p<<1,l,r),right=query3(p<<1|1,l,r);
	now.L=left.L,now.R=right.R;
	now.s2=max(max(left.s2,right.s2),left.R+right.L);
	if(left.len>=2) now.LL=left.LL;
	else now.LL=left.R+right.L;
	if(right.len>=2) now.RR=right.RR;
	else now.RR=left.R+right.L;
	now.s3=max(max(left.s3,right.s3),max(left.RR+right.L,left.R+right.LL));	
	now.w=left.w+right.w;
	return now;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	build(1,1,n);
	for(int i=1;i<=m;++i){
		int op,l,r;
		scanf("%d %d %d",&op,&l,&r);
		if(op==1){
			int x;
			cin>>x;
			add(1,l,r,x);
		}else{
			ll maxx2=query2(1,l,r).s2;
			ll maxx3=query3(1,l,r).s3;
			if(maxx2*3>maxx3*2){
				if(maxx2%2==0) cout<<maxx2/2<<"/"<<"1\n";
				else cout<<maxx2<<"/"<<"2\n";
			}else{
				if(maxx3%3==0) cout<<maxx3/3<<"/"<<"1\n";
				else cout<<maxx3<<"/"<<"3\n";
			}
		}
	}
	return 0;
}

C 沙奈朵

题意

沙奈朵给定一个长度为 \(n\) 的序列 \(a\),每个位置都是一个 \([1,n]\) 内的整数。
定义 \(f(i,j)\) 表示有多少个 \(x\) 满足 \(i\le x<j\)\(a_x \ne a_{x+1}\)
\(m\) 次操作:
1. 1 l r x:表示将 \(l\) 位置修改为 \(x\)
2. 2 l r x:表示查询区间 \([l,r]\) 中,对于任意 \(l\le i<j\le r\),且 \(a_i=a_j=x\)\(f(i,j)\) 的和。
注意,为了让两种操作读入一致,\(1\) 操作的 \(r\) 卵用没有。
数据范围:
对于 \(20\%\) 的数据,没有 \(1\) 操作。
对于全部数据:\(1\le n,m \le 5\times 10^5,1\le l\le r\le n,1\le a_i,x \le n\)

输入格式
第一行两个数 \(n,m\)
第二行 \(n\) 个用空格隔开的数表示序列 \(a\)
之后 \(m\) 行,每行四个用空格隔开的数 \(opt,l,r,x\) 表示一次操作。

输出格式
对每个 \(2\) 操作,输出一行一个数表示答案。

样例输入
10 10
2 1 2 1 8 3 2 1 2 2
2 6 9 2
2 3 10 2
2 2 10 2
2 1 3 2
2 4 10 1
1 2 4 2
2 3 10 2
2 2 7 1
2 2 7 2
2 3 6 2

样例输出
2
20
20
2
4
20
0
8
0

考场思路

暴力。

代码(\(20\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 500001;
int a[N];
int n,m;
int opt[N],l[N],r[N],x[N];
int baoli_f(int l,int r)
{
	int res = 0;
	int i;
	for(i=l;i<r;i++)
		res += a[i]!=a[i+1];
	return res;
}
int main()
{
	cin>>n>>m;
	int i,j;
	for(i=1;i<=n;i++)
		cin>>a[i];
	for(i=1;i<=m;i++)
		cin>>opt[i]>>l[i]>>r[i]>>x[i];
	int tzfakioi;
	for(tzfakioi=1;tzfakioi<=m;tzfakioi++)
	{
		if(opt[tzfakioi]==1)
			a[l[tzfakioi]] = x[tzfakioi];
		else
		{
			int ans = 0;
			for(i=l[tzfakioi];i<r[tzfakioi];i++)
			{
				for(j=i+1;j<=r[tzfakioi];j++)
				{
					if(a[i]==a[j]&&a[i]==x[tzfakioi])
						ans += baoli_f(i,j);
				}
			}
			cout<<ans<<endl;
		}
	}
	return 0;
}

正解

先考虑 \(O(n)\) 来求区间 \([l,r]\) 的解。

先考虑维护以下信息:
1. 从 \(l\)\(i\) 的所有值为 \(x\) 的数到 \(i\) 的颜色段数量和\(a\)
2. 从 \(l\)\(i\) 有多少个值为 \(x\) 的数 \(num\)
3. 区间 \([l,i]\) 的解 \(ans\)
可以发现以上信息是好维护的。
复杂度为 \(O(nm)\)

具体的维护方式:

  • \(a_{i+1}=x\),则 \(ans = ans+a,num = num+1\)
  • \(a_{i+1} \ne a_i\),则 \(a = a+num\)
    接下来考虑分块,即对于区间维护以上信息。

考虑要维护的信息:
1. 从 \(l\)\(r\) 的所有值为 \(x\) 的数到 \(r\) 的颜色段数量和 \(a_i\)
2. 从 \(r\)\(l\) 的所有值为 \(x\) 的数到 \(l\) 的颜色段数量和 \(b_i\)
3. 从 \(l\)\(r\) 的颜色段数量 \(c\)
4. 从 \(l\)\(r\) 有多少个值为 \(x\) 的数 \(num\)
5. 区间 \([l,r]\) 的解 \(ans\)
6. 区间最左边和最右边的元素 \(left\)\(right\)
发现这些信息也是好维护的。
具体的维护方式:

  • \(new_{ans} = l_{ans}+r_{ans}+l_a\times r_{num}+r_\times l_{num}+[l_{right}\ne r_{left}]\times l_{num}\times r_{num}\)
  • \(new_a = r_a+l_a\times(r_c+[l_{right}\ne r_{left}])\)
    \(new_b\) 的维护方法类似。
    其他变量的维护方法都是普通的,不再赘述。
    再捋一遍思路:
  • 对于每个块维护以上信息,每次修改的时候跑一遍(做法与一开始的 \(O(n)\) 求去见答案类似,不再赘述),然后每次询问散块暴力跑,整块用合并方式来跑。
    复杂度 \(O(\sqrt(n)(n+m))\)
    代码:
点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 5e5 + 10, maxs = 720;
int n, m, B, A[maxn];

struct node {
	int opt, l, r, x;
	inline void input() {cin >> opt >> l >> r >> x; if(opt == 1) r = l; }
} Q[maxn];

struct Msg {
	int num, siz, l, r;
	long long ans, toL, toR;
	Msg(int Num = 0, int Siz = 0, int L = 0, int R = 0, long long Ans = 0, long long ToL = 0, long long ToR = 0) :
	num(Num), siz(Siz), l(L), r(R), ans(Ans), toL(ToL), toR(ToR) {}
//num: 该颜色数量, siz: 除了右端点所在段的颜色段个数, l: 左侧颜色, r: 右侧颜色
//ans: 区间答案, toL: 该颜色到左边界颜色段个数-1的和, toR: 该颜色到右边界颜色段个数和
	Msg operator + (const Msg &rhs) {
		Msg res; const Msg &lhs = *this;
		res.l = lhs.l, res.r = rhs.r, res.num = lhs.num + rhs.num, res.siz = lhs.siz + rhs.siz + (lhs.r != rhs.l);
		res.ans = lhs.ans + rhs.ans + lhs.toR * rhs.num + rhs.toL * lhs.num - (lhs.r == rhs.l) * lhs.num * rhs.num;
		res.toL = lhs.toL + rhs.toL + rhs.num * (lhs.siz + 1 - (lhs.r == rhs.l));
		res.toR = rhs.toR + lhs.toR + lhs.num * (rhs.siz + 1 - (lhs.r == rhs.l));
		return res;
	}
	bool operator == (const Msg &rhs) const {
		return num == rhs.num && siz == rhs.siz && l == rhs.l && r == rhs.r && ans == rhs.ans && toL == rhs.toL && toR == rhs.toR;
	}
} mes[maxn], ans[maxn];
ostream& operator <<(ostream& out, const Msg &x) {
	out << x.num << ' ' << x.siz << ' ' << x.l << ' ' << x.r << ' ' << x.ans << ' ' << x.toL << ' ' << x.toR;
	return out;
}
int Count[maxn];

inline Msg Build(int l, int r) {
	for(int i = l; i <= r; i ++) mes[A[i]] = Msg();
	int lst = 0, siz = 0;
	for(int i = l; i <= r; i ++) {
		if(lst == A[i]) mes[A[i]] = mes[A[i]] + Msg(1, 0, A[i], A[i], 0, 0, 1);
		else {
			if(mes[A[i]] == Msg()) {
				mes[A[i]] = Msg(1, siz, A[l], A[i], 0, siz, 1); siz ++, lst = A[i];
			} else {
				int add = siz - mes[A[i]].siz - 1; mes[A[i]] = mes[A[i]] + Msg(1, add, 0, A[i], 0, add, 1); 
				lst = A[i], siz ++; 
			}
		}
	}
	for(int i = l; i <= r; i ++) if(siz - 1 != mes[A[i]].siz) {
		int add = siz - mes[A[i]].siz - 2;
		mes[A[i]] = mes[A[i]] + Msg(0, add, 0, A[r], 0, 0, 0);
	}
	return Msg(0, siz - 1, A[l], A[r], 0, 0, 0);
}

inline Msg Query(int l, int r, int x) {
	Msg res(A[l] == x, 0, A[l], A[l], 0, 0, A[l] == x);
	for(int i = l + 1; i <= r; i ++) 
		res = res + Msg(A[i] == x, 0, A[i], A[i], 0, 0, A[i] == x);
	return res;
}

inline void calc(int l, int r) {
	Msg T = Build(l, r);
	for(int i = 1; i <= n; i ++) Count[i] = 0;
	for(int i = l; i <= r; i ++) Count[A[i]] ++;
	for(int kase = 1; kase <= m; kase ++) {
		auto &q = Q[kase];
		if(q.r < l || q.l > r) continue;    //和现在处理的区间没关系.
		
		if(q.opt == 1) {
			Count[A[q.l]] --; Count[q.x] ++;
			A[q.l] = q.x, T = Build(l, r);
			continue;
		}
		if(q.l >= l && q.l <= r) {  //左端散块
			ans[kase] = Query(q.l, min(r, q.r), q.x);
		} else if(q.r >= l && q.r <= r) { //右端散块
			ans[kase] = ans[kase] + Query(l, q.r, q.x);
		} else if(q.l < l && q.r > r) { //中间的整块
			if(Count[q.x] == 0) ans[kase] = ans[kase] + T;
			else ans[kase] = ans[kase] + mes[q.x];
		} else {
			cerr << l << ' ' << r << ' ' << q.l << ' ' << q.r << '\n';
			assert(false);
		}
	}
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	
	cin >> n >> m; B = sqrt(n);
	for(int i = 1; i <= n; i ++) cin >> A[i];
	for(int i = 1; i <= m; i ++) Q[i].input();
	
	for(int l = 1, r = B; l <= n; l += B, r += B) 
		calc(l, min(r, n));
	
	for(int i = 1; i <= m; i ++)
		if(Q[i].opt == 2) cout << ans[i].ans << '\n';
	
	return 0;
}

D 绒绒鸹

题意

\(n\) 个点,每一个点都一个父亲,每次操作在一个点上放一个绒绒鸹,随即立刻输出这个点的绒绒鸹的数量。接下来,每一个绒绒鸹都会同时向他所在点的父亲移动。
数据范围:\(1\le n,m \le 5\times 10^5,1\le a_i \le n\)

输入
第一行 \(n\)
第二行,序列 \(a\)
第三行 \(m\)
第四行,每次输入一个整数 \(x\),表示 \(x\) 位置放一只绒绒鸹。
本题强制在线,即对于输入的 \(m\) 次操作的数,第 \(i\) 次的数需要异或上第 \(i-1\) 次操作的答案。

输入格式
第一行一个整数 \(n\)
接下来 \(n\) 行,第 \(i\) 行表示 \(a_i\)
接下来一行一个整 \(m\)
接下来 \(m\) 行每行一个整数 \(x\),表示在 \(x\) 位置放置一只绒绒鸹。
本题强制在线,即对于输入的 \(m\) 次操作的数,第 \(i\) 次的数需要异或上第 \(i-1\) 次操作的答案。

输出格式
输出 \(m\) 行,第 \(i\) 行输出一个整数,表示第 \(i\) 次操作的答案。

样例输入
6
1 2 1 3 3 6
5
1 4 7 3 7

样例输出
1 1 1 1 2

考场思路

最后三分钟写的,卡点交,少一个加号,爆零。
开三个数组 \(a,b,c\)\(a\) 是原序列,\(b\) 是操作时的序列,\(c\) 是跳完之后的序列。
然后模拟,注意不要搞混了。
代码(\(0\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,a[250001],b[250001],c[250001]; 
int ans[250001];
int main()
{
	int i,j,x;
	cin>>n;
	for(i=1;i<=n;i++)
		cin>>a[i];
	int la = 0;
	cin>>m;
	for(i=1;i<=m;i++)
	{
		cin>>x;
		x ^= la;
		b[x]++;
		la = b[x];
		cout<<b[x]<<endl;
		memset(c,0,sizeof c);
		for(j=1;j<=n;j++)
			c[a[j]] += b[j];
		for(j=1;j<=n;j++)
			b[j] = c[j];
	}
	return 0;
}

正解做法一

其实图就是一个基环树森林,环上的做法是简单的。
考虑树上如何处理:
考虑什么时候点 \(y\) 会对点 \(x\) 产生贡献,发现当 \(time_y-time_x=dep_y-dep_x\) 时才会产生贡献,即此时只要开个桶即可。
可以大力树剖来计算答案,复杂度 \(O(n\log^2 n)\)

正解做法二

\(n\) 个位置构成环套树森林,可以拆分为一些有根树和有向环,且有根树的根的后继为有向环上的点。
对有根树进行轻重链剖分,对于一个球,经过 \(O(\log n)\) 条轻边后可以到达环上。
对于一条重链或一个环,维护一个序列表示每个位置的球的个数。
当一个球经过一条轻边时,修改离开的重链和进入的重链环数。
每次操作后,环对应的序列会循环移动一位,重链对应的序列会向深度较浅的方向移动一位(由于球离开重链导致移位出界的情况已被处理,重链上也可视为循环移位)。
这些移位不需要直接处理,只需要在需要修改/查询序列的某个位置时,将操作位置加上对应的偏移量即可。
另一个做法是对环用数组维护,考虑去掉环剩下的每棵树,每次加入球就在对应位置记录这个球的加入时间+离根的距离,查询时查一个点子树内加入时间+离根的距离等于相应值得球的个数就行了。
对每个值 \(x\),用支持维护有序集合、查询区间元素个数的数据结构维护加入时间+离根的距离 \(=x\) 的球的 DFS 序编号,查询为对某个 \(x\),问 DFS 序在区间内球的个数。
时间复杂度 \(O(n+m\log n)\),空间复杂度 \(O(n+m)\)

代码:

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 10, M = 1e6 + 10;
int n, m, f[N], id[N], pos[N], A[N];
bool in_tr[N];
vector<int> update[N + M], vec[2 * N];

int h[N], e[M], ne[M], idx;
inline void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++; }

int siz[N], hson[N], top[N], dep[N];
inline void Init() {
    static int ind[N];
    for(int i = 1; i <= n; i ++) ind[f[i]] ++;
    static int que[N], ql = 0, qr = 0; for(int i = 1; i <= n; i ++) if(!ind[i]) que[++qr] = i, siz[i] = 1;
    while(ql != qr) {
        int u = que[++ql]; in_tr[u] = true;
        if(!(--ind[f[u]])) que[++qr] = f[u];
        siz[f[u]] += siz[u]; if(siz[hson[f[u]]] < siz[u]) hson[f[u]] = u;
    }
    
    for(int i = qr; i; i --) if(!top[que[i]]) { int u = que[i];
        int v = u; 
        while(v) {
            top[v] = u, dep[v] = dep[f[v]] + 1;
            pos[v] = vec[u].size(), vec[u].emplace_back(v), id[v] = u;
            v = hson[v];
        }
    }

    for(int i = 1; i <= n; i ++)
        if(!id[i]) { assert(!in_tr[i]);
            int u = i;
            while(!id[u]) {id[u] = n + i, vec[id[u]].emplace_back(u); u = f[u]; }
            reverse(vec[id[u]].begin(), vec[id[u]].end());
            for(int j = 0; j < vec[id[u]].size(); j ++) pos[vec[id[u]][j]] = j;
        }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    memset(h, -1, sizeof h);

    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> f[i];
    cin >> m; Init();

    int lastans = 0;
    for(int T = 0; T < m; T ++) {
        for(auto &Id : update[T]) {
            int x = f[vec[Id][0]], y = vec[Id][(T - 1) % vec[Id].size()];
            //cerr << x << ' ' << y << ' ' << get(x, T + 1) << '\n';
            A[vec[id[x]][(pos[x] + T) % vec[id[x]].size()]] += A[y], A[y] = 0;
            if(in_tr[x]) update[T + (dep[x] - dep[top[x]]) + 1].emplace_back(id[x]);
        }

        int x, y; cin >> x; x = x xor lastans; int Id = id[x], p = pos[x];
        y = vec[Id][(p + T) % vec[Id].size()]; 
        if(in_tr[x]) update[T + (dep[x] - dep[top[x]]) + 1].emplace_back(id[x]);
        cout << (lastans = ++A[y]) << '\n';

        //for(int i = 1; i <= n; i ++) cerr << A[i] << " \n"[i == n];
    }

    return 0;
}

Day 2

数据结构+DP。
\(100+60+20+0=180\) pts。

评价B

Rank 32/58。

A OBG

题意

小 L 在某论坛 codeforces 上一共发出了 \(n\) 条评论,第 \(i\) 条评论有 \(a_i\) 个赞,\(b_i\) 个踩,其中一个赞会抵消一个踩,一个踩也会抵消一个赞。
对于每条评论,如果抵消后留下的是赞,则它的 Contribuion 值加上赞的个数整除 \(10\) 的下取整;如果留下的是踩,那么它的 Contribution 减去踩得个数整除 \(10\) 的下取整。
数据范围:\(1\le n \le 100,a_i \le 10^8,b_i \le 10^8\)

输入格式
第一行,一个整数 \(n\),代表有 \(n\) 条评论。
第二行,\(n\) 个整数 \(a_i(1\leq i \leq n)\)
第二行,\(n\) 个整数 \(b_i(1\leq i \leq n)\)

输出格式
一个整数,表示一共获得的 Contribution。

样例输入
2
11 1
1 11

样例输出
0

考场思路

模拟。

代码(\(100\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,cnt;
struct node
{
	int a,b,sum;
}a[101];
main()
{
	int i;
	cin>>n;
	for(i=1;i<=n;i++)
		cin>>a[i].a;
	for(i=1;i<=n;i++)
		cin>>a[i].b;
	for(i=1;i<=n;i++)
		a[i].sum = a[i].a-a[i].b;
	for(i=1;i<=n;i++)
	{
		if(a[i].sum>=0)
			cnt += a[i].sum/10;
		else
			cnt += a[i].sum/10; 
	}
	cout<<cnt<<endl;
	return 0;
}

正解

和思路一样。

B FTT

题意

\(n\) 只青蛙排成一个序列,第 \(i\) 只青蛙的叫声为 \(a_i\),其中任意两只青蛙的叫声都不一致。
现在要选 \(k+1\) 只青蛙作为一个集训队,集训队的成员编号为 \(0,1,\dots,k\)
集训队要求:对于第 \(i\) 名队员和第 \(j\) 名队员,\(i<j\)\(a_i<a_j\)
你需要求出有多少种选出青蛙集训队的方案,方案数对 \(998244353\) 取模。
数据范围:\(1\le n \le 10^5,0 \le k \le 10,1\le a_i \le n\)

输入格式
第一行输入两个数 \(n,k\)
接下来输入 \(n\) 行,每行一个数,其中第 \(i\) 行输入 \(a_i\),表示第 \(i\) 只青蛙的叫声。

输出格式
输出一个数,答案对 \(998244353\) 取模后的结果。

样例输入
5 2
1 2 3 5 4

样例输入
7

考场思路

观察到此题就是一个 LIS,因此考虑 DP。
\(dp_{i,j}\) 为前 \(i\) 个青蛙,选了 \(j\) 个青蛙的方案数,容易得:

  • 可以三重循环求出。为目前选了多少只青蛙和 \(i\)\(j\) 的位置。
  • \(a_k<a_i\) 时,\(dp_{i,j} = (dp_{i,j}+dp_{k,j-1})%mod\),注意初始为 \(dp_{0,0} = 1\)
  • 答案为所有的 \(dp_{i,k}\) 的和,注意取模。

还有一个坑点:是 \(k+1\) 只青蛙而不是 \(k\) 只青蛙!!

代码(\(60\) 分,裸 DP,代码变量与题意略有出入):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
int n,K;
int a[100001],dp[10001][11];
int ans;
main()
{
	cin>>n>>K;
	K++;
	int i,j,k;
	for(i=1;i<=n;i++)
		cin>>a[i];
	dp[0][0] = 1;
	for(j=1;j<=K;j++)
	{
		for(i=1;i<=n;i++)
		{
			for(k=0;k<i;k++)
			{
				if(a[k]<a[i])
					dp[i][j] = (dp[i][j]+dp[k][j-1])%mod;
			}
		}
	}
	for(i=1;i<=n;i++)
		ans += dp[i][K],ans %= mod;
	cout<<ans%mod<<endl;
    return 0;
}

正解

用树状数组或线段树优化,这里用树状数组比较简单。
我们需要开很多个树状数组,其中:

  • find(r,l) 表示查询 \(l\)\(r\) 区间的和。
  • modify(x,y,z) 作用是对于第 \(z\) 个树状数组的第 \(x\) 个位置,将值加上 \(y\)
    然后就很简单了,初始化就让第一个树状数组的第一个位置为 \(1\),最后输出 find(n+1,k+1)。注意为了好实现,find() 倒序实现。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
int a,b[100005];
int c[100005][15];
int mod = 998244353;
int lowbit(int x)
{
	return x & -x;
}
void modify(int x,int y,int z)
{
	int i;
	for(i=x;i<=n+1;i+=lowbit(i))
		c[i][z] += y,c[i][z] %= mod;
	return;
}
int find(int x,int y)
{
	int res = 0;
	while(x)
		res += c[x][y],res %= mod,x -= lowbit(x);
	return res;
}
main()
{
	cin>>n>>k;
	k++;
	modify(1,1,1);
	int i,j;
	for(i=1;i<=n;i++)
	{
		cin>>a;
		for(j=1;j<=k;j++)
			modify(a+1,find(a,j),j+1); 
	}
	cout<<find(n+1,k+1)<<endl;
	return 0;
}

C G-CAT

题意

有一个长度为 \(n\) 的序列,第 \(i\) 个位置为 \(c_i\)
\(q\) 次询问,每次给定一个 \(1\le l\le r\le n\),考虑子序列 \(c_l,c_l+1,\dots,c_r\),你需要选出若干个互不相交的区间,满足每个区间的元素之和为 \(0\)。要求最大化选择区间的数量,求出你可以选出多少区间。
数据范围:\(1\le n,q \le 4\times 10^5,-10^9 \le c_i \le 10^9,1\le l_i \le r_i \le n\)

输入格式
输入的第一行包含一个整数 \(n\)
接下来一行,包含 \(n\) 个整数 \(c_1,c_2,\cdots, c_n\)
接下来一行,包含一个整数 \(q\)
接下来 \(q\) 行,每行两个整数 \(l, r\),描述一组询问。

输出格式
对于每组询问,输出一行一个整数,表示答案。

样例输入
10
1 2 -3 0 1 -4 3 2 -1 1
3
1 10
1 5
2 9

样例输出
4
2
2

考场思路

DP。
样例全过,大样例答案正确,超时。
但是爆零,就不讲解思路了。

代码(\(0\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
int a[400088];
int dp[400088];
main()
{
	cin>>n;
	int i;
	for(i=1;i<=n;i++)
		cin>>a[i];
	cin>>q;
	while(q--)
	{
		int l,r;
		scanf("%d %d",&l,&r);
		memset(dp,0,sizeof(dp));
		unordered_map<int,int> last;
		last[0] = 0;
		int sum = 0;
		for(i=l;i<=r;i++)
		{
            sum += a[i];
            if (last.find(sum)!=last.end())
                dp[i] = dp[last[sum]]+1;
            dp[i] = max(dp[i],dp[i-1]);
            last[sum] = i;
        }
        printf("%d\n",dp[r]);
	}
	return 0;
}

正解

\(nxt\) 数组表示 \(nxt_i\)\(i\) 右边的第一个满足 \(\sum\limits_{j=i}^{nxt_i} a_j = 0\) 的数。
考虑选择了 \(i\) 之后的选择,不难发现是 \(\max\limits_{nxt_i+1\le i\le n} nxt_i\)
发现选择关系构成了一棵树,建树倍增判断是否走出区间即可。
时间复杂度 \(O((n+m)\log n)\)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int nxt[400005],st[400005];
ll sum[400005];
int n,Q;
struct node
{
	int l,r,s;
}q[400001];
main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n;
	int i,j,k;
	for(i=1;i<=n;i++)
	{
		cin>>sum[i];
		sum[i] += sum[i-1];
	}
	multiset<ll> s{0};
	int l,r;
	l = 1,r = 0;
	for(;l<=n;l++)
	{
		while(r<=n)
		{
			if(s.count(sum[r])>=2)
				break;
			r++;
			s.emplace(sum[r]);
		}
		nxt[l] = r+1;
		s.erase(s.find(sum[l-1]));
	}
	cin>>Q;
	for(i=1;i<=Q;i++)
	{
		cin>>q[i].l>>q[i].r;
		q[i].s = 0;
	}
	st[n+1] = st[n+2] = n+2;
	for(j=log2(n)+1;j>=0;j--)
	{
		for(i=1;i<=n;i++)
			st[i] = nxt[i];
		for(k=1;k<=j;k++)
			for(i=1;i<=n;i++)
				st[i] = st[st[i]];
		for(i=1;i<=Q;i++)
		{
			if(st[q[i].l]<=q[i].r+1)
			{
				q[i].s += (1<<j);
				q[i].l = st[q[i].l];
			}
		}
	}
	for(i=1;i<=Q;i++)
		cout<<q[i].s<<'\n';
	return 0;
}

先别走啊!!
在输出答案时,我一开始用的 cout<<q[i].s<<endl;,结果 \(100\to 50\);改成 cout<<q[i].s<<'\n' 就过了。
警钟撅烂磨成粉!!!
做题前一定要 #define endl '\n'!!!!

OIL

题意

给你一个长度为 \(n\) 的序列 \(a\)
定义 \(\verb!maxpre!(l,r)\) 是区间 \([l,r]\) 的最大前缀和;\(\verb!maxsuf!(l,r)\) 是区间 \([l,r]\) 的最大后缀和。
最大前缀和最大后缀都可以是空串。
求:$$\sum\limits_{l=1}^n \sum\limits_{r-l+1}^n \sum\limits_{i=l}^{r-1}\times \verb!maxpre(l,i)! \times \verb!maxsuf(i+1,r)!$$
答案对 \(10^9+7\) 取模。
数据范围:\(1\le n\le 10^5,-10^9 \le a_i \le 10^9\)

输入格式
第一行一个数 \(n\) 表示这个序列的长度。
之后一行包含 \(n\) 个整数,表示这个序列。
保证序列中所有元素都在 \([-10^9,10^9]\) 中。

输出格式
输出一行一个整数表示答案。

输入样例
5
1 -2 3 -4 5

输出样例
76

考场思路

不会,随便输出了一个数。

代码(\(0\) 分):

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
	cout<<219352;
	return 0;
}

正解

考虑对于 \(i\) 求出 \(pre_i=\sum_{j=1}^i \verb!maxpre!_j\)\(suf_i = \sum_{j=i}^n \verb!maxsuf!_i\)
\(\sum_{i=1}^{n-1} pre_i \times suf_{i+1}\) 即为答案(以下表达中 \(\verb!maxpre!\) 可以 \(\le 0\))。
考虑那些 \(\verb!maxpre!\) 会对 \(pre\) 产生贡献,发现要 \(\verb!maxpre! \ge 0\)
考虑平衡树,从 \(1\) 枚举到 \(n\),之前的所有 \(\verb!maxpre!\) 都加入 \(a_i\) 然后插入当前的 \(a_i\),然后查询大于零的数的和即为 \(pre_i\)
全局加可以转化为打 \(tag\)\(suf\)\(pre\) 的求法类似,时间复杂度为 \(O(n\log n)\)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int mod=1e9+7;

int n;
ll a[100005];
priority_queue<pair<ll,int> > Q;
ll f[100005],g[100005],ans,tag;

int main(){
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i];
	for(int i=1;i<=n;++i){
		int cnt=0;
		Q.push({-tag,1});
		tag=(tag+a[i]);
		while(!Q.empty()&&(Q.top().first+tag)>0){
			ans=(ans+(Q.top().first+tag)%mod*Q.top().second%mod)%mod;
			cnt+=Q.top().second;Q.pop();
		}
		if(cnt) Q.push({-tag,cnt});
		f[i]=ans;
	}
	while(!Q.empty()) Q.pop();
	ans=0,tag=0;
	for(int i=n;i>=1;--i){
		int cnt=0;
		Q.push({-tag,1});
		tag=(tag+a[i]);
		while(!Q.empty()&&(Q.top().first+tag)>0){
			ans=(ans+(Q.top().first+tag)%mod*Q.top().second%mod)%mod;
			cnt+=Q.top().second;Q.pop();
		}
		if(cnt) Q.push({-tag,cnt});
		g[i]=ans;
	}
	ans=0;
	for(int i=1;i<n;++i)
		ans=(ans+f[i]*g[i+1]%mod)%mod;
	cout<<ans<<endl;
	return 0;
}

Day3

100+0+0+0=100 rk31/58

评价:B-

A

题目

Y 国决定入侵 W 国的防卫系统。现在,需要输入一串安保密码。
W 国密码输入比较特殊,由 \(4\times 4\) 个格子组成,某些格子上悬着“控制单元”,某些格子是解密格子。必须移动这些“控制单元”使每个解密格子上都悬着“控制单元”才能破开 W 国的防卫系统。
每次移动,你可以选择一个悬着“控制单元”的格子,并选择其一个没有悬着“控制单元”的邻居格子,将“控制单元”移动到它上面。两个格子是邻居当且仅当它们有共用的边。
Y 国已经成功破译出哪些格子是解密格子,但是移动一次“控制单元”非常麻烦。所以你需要帮助 Y 国求出,最少移动几次“控制单>元”可以使得每个解密格子上都有“控制单元”。

开头四行,每行四个字符,如果第 \(i\) 行第 \(j\) 列为 1,说明初始时第 \(i\) 行第 \(j\) 列的格子上悬着“控制单元”;如果第 \(i\) 行第 \(j\) 列为 0,说明初始时第 \(i\) 行第 \(j\) 列的格子上没有悬着“控制单元”。
接下来四行,每行四个字符,如果第 \(i\) 行第 \(j\) 列为 1,说明第 \(i\) 行第 \(j\) 列的格子是解密格子;如果第 \(i\) 行第 \(j\) 列为 0,说明第 \(i\) 行第 \(j\) 列的格子上不是解密格子。

输出一个整数表示最小移动次数。

样例输入

0100

0000

0001

0000

0000

0001

0001

0000

输出

3

概括

给定 \(4 × 4\) 的网格,有若干个格子放着石头,你每次可以移动石头到相邻的没有石头的格子。给定初始状态和目标状态,求最小移动次数。

思路:

进行广搜,用二进制记录状态,复杂度 \(O(2^16 × 16)\)

点击查看代码
#include <iostream>
#include <bitset>
#include <queue>
#include <unordered_set>
using namespace std;

const int N = 4;

int bfs(bitset<N * N> initial_control, bitset<N * N> decryption_cells) {
    queue<pair<bitset<N * N>, int>> q;
    q.push({initial_control, 0});

    unordered_set<bitset<N * N>> visited;
    visited.insert(initial_control);

    vector<vector<int>> moves = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    while (!q.empty()) {
        bitset<N * N> current_control = q.front().first;
        int moves_count = q.front().second;
        q.pop();

        if (current_control == decryption_cells)
            return moves_count;

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (current_control.test(i * N + j)) {
                    for (auto move : moves) {
                        int ni = i + move[0];
                        int nj = j + move[1];
                        if (ni >= 0 && ni < N && nj >= 0 && nj < N) {
                            bitset<N * N> next_control = current_control;
                            next_control.reset(i * N + j);
                            next_control.set(ni * N + nj);
                            if (visited.find(next_control) == visited.end()) {
                                visited.insert(next_control);
                                q.push({next_control, moves_count + 1});
                            }
                        }
                    }
                }
            }
        }
    }

    return -1; // If no solution is found
}

int main() {
    bitset<N * N> initial_control;
    bitset<N * N> decryption_cells;

    // Read the initial control and decryption cells grid
    for (int i = 0; i < N; i++) {
        string row;
        cin >> row;
        for (int j = 0; j < N; j++) {
            if (row[j] == '1')
                initial_control.set(i * N + j);
        }
    }

    for (int i = 0; i < N; i++) {
        string row;
        cin >> row;
        for (int j = 0; j < N; j++) {
            if (row[j] == '1')
                decryption_cells.set(i * N + j);
        }
    }

    int result = bfs(initial_control, decryption_cells);
    cout << result << endl;

    return 0;
}

B

题目

W 国和 Y 国是敌对关系,两国之间爆发了战争。

W 国军队掉入了 Y 国的陷阱!

陷阱由 \(n\) 个节点构成,这些节点由 \(m\) 条隧道连接。第 \(i\) 条隧道连接着编号分别为 \(u_i,v_i\) 的节点,保证任意两个节点可以通过隧道互相到达。现在 W 国军队在编号为 \(1\) 的节点。
假如 W 国军队可以到达每个节点,那他们就可以把所有节点都破坏,逃出陷阱。为了防止 W 国军队逃出,Y 国在每条隧道里都放着屏障,第 \(i\) 条隧道里的屏障强度为 \(a_i\)。只要一个隧道里的屏障不失效,W 国军队就不能使用这条隧道移动。
然而,W 国军队携带着一个总能量为 \(x\) 的破障机,它可以消耗能量来使屏障失效。具体的,如果想要让一个强度为 \(y\) 的屏障失效,破障机就要消耗 \(y\) 的能量。破障机不能透支能量,所以,在消耗 \(y\) 的能量之前,它剩余能量必须大于等于 \(y\)
为了防止 W 国军队逃出,Y 国决定使用强化核心。第 \(i\) 条隧道里的屏障与强化核心的适配度为 \(b_i\),当 Y 国使用了一个强化核心,则所有屏障都会强化,对于每个屏障,其强度上升其适配度。具体的,假如 Y 国使用了 \(k\) 个强化核心,则对于每个屏障,其强度会变成 \(a_i+kb_i\)
请你求出,Y 国最少使用几个强化核心,可以使得 W 国军队无法逃出。

样例输入1

3 3 100

1 2 3 1

2 3 1 2

1 3 3 2

样例输出1

33

概括

给定 \(n\) 个点 $m $条边的无向连通图以及一个正整数 $$x,每条边有两个权值\(a_i, b_i\),求出最小的$ $k,使得以 \(a_i + kb_i\) 为边权计算最小生成树的大小比 $x $大。
\(1 ≤ n, m ≤ 3 × 105, 1 ≤ ui, vi ≤ n, 1 ≤ ai,bi ≤ 109, 1 ≤ x ≤ 1014。\)

思路

二分答案,然后求最小生成树判定就行了。
总复杂度 \((m log m log x)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5 + 10, maxm = 6e5 + 10;
int n, m; long long x;

long long mid;
struct Edge {
	int from, to; long long a, b;
	Edge(int u, int v, long long A, long long B) : from(u), to(v), a(A), b(B) {}
	bool operator < (const Edge &rhs) const {return a + mid * b < rhs.a + mid * rhs.b; }
};
vector<Edge> edges;

int p[maxn];
int find(int x) {return x == p[x] ? x : p[x] = find(p[x]); }

inline bool Kruskal() {
	sort(edges.begin(), edges.end());
	for(int i = 1; i <= n; i ++) p[i] = i;
	int siz = n; long long ans = 0;

	for(auto &e : edges) if(find(e.from) != find(e.to))  {
		siz --, p[find(e.from)] = find(e.to), ans += e.a + mid * e.b;
		if(siz == 1 || ans > x) break;
	}
	return ans > x;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);

	cin >> n >> m >> x;
	long long L = 0, R = 0;
	for(int i = 0; i < m; i ++) {
		int u, v; long long a, b; cin >> u >> v >> a >> b;
		R = max(R, (x - a) / b + 1);
		edges.emplace_back(Edge(u, v, a, b));
	}

	while(L < R) {
		mid = (L + R) >> 1;
		if(Kruskal()) R = mid;
		else L = mid + 1;
	}
	cout << L << '\n';

	return 0;
}

C

题目

W 国在战场有 \(n\) 个阵地,编号为 \(1,2,\dots,n\)。这些阵地由 \(m\) 条隧道连接,第 \(i\) 条隧道连接着编号分别为 \(u_i,v_i\) 的阵地,长度为 \(w_i\)。任意两个阵地可以通过隧道互相到达。
Y 国决定突袭,现在 Y 国的军队到达了编号为 \(1\) 的阵地。W 国在编号为 \(2,3,\dots,n\) 的阵地里隐藏了一个大本营。Y 国希望能够突袭大本营。
为了防止突袭计划失败,Y 国决定兵分两路来包围 W 国的大本营,具体的,假如 W 国的大本营为 \(x\),两路军队会分别计划两条路线从 \(1\) 前往 \(x\)。迟则生变,所以两路军队都会按最短路进军。同时,两路军队计划的路线除了编号为 \(1\) 的阵地和编号为 \(x\) 的阵地之外,不能经过相同的阵地,这样即使有一路军队受阻,另一路军队也能继续突袭。
然而,两条除了起点终点无交的最短路并不一定能找到,同时,Y 国还没有确定 W 国的大本营在哪里。所以,你需要求出,是否 \(\forall 2\le i\le n\),Y 国都能找到两条 \(1\)\(i\) 的路线,使得两条路线的长度和 \(1\)\(i\) 的最短路相同,且两条路线除了 \(1\)\(i\) 之外无交。

概括

给定一个 n 个点 m 条边的带边权的连通无向图,求是否满足,\(2 ≤ i ≤ n\)可以找到两条除了 $1和 \(i\)外无交的从 \(1到 i\)的最短路。
\(1≤ n, m ≤ 3 × 105, 1 ≤ wi ≤ 109。\)

思路

建立最短路 DAG,假如存在一个点只有一个前驱,那么显然题目条件不可能满足。
假如所有点都有至少两个前驱,那么一定可以,我们可以通过构造证明:
取两条 \(1\)\(i\) 的最短路,假设两条最短路除了$ i$ 之外最后的交点为 \(k\),令第一条路线中 \(k\) 的后继是$ j$,假如 \(j\) 存在的另一个前驱为 \(l\),把第一条路线换成$ 1$到 \(l\) 的最短路再接上第一条路线本来 \(j\)\(i\) 的路线。发现这样替换后,两个路线的最后交点会越来越提前,直到为 \(1\) 即可。
复杂度 \(O((n + m)log n)\)

代码

点击查看代码
#include <bits/stdc++.h>

using namespace std;
typedef pair<long long, int> pli;

const int maxn = 3e5 + 10, maxm = 6e5 + 10;
int n, m, h[maxn], e[maxm], ne[maxm], w[maxm], idx;
inline void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++; }

long long dist[maxn]; bool vis[maxn];
int c[maxn], wh[maxn], pre[maxn];
inline bool Dijkstra(int S = 1) {
	memset(dist, 0x3f, sizeof dist), memset(vis, false, sizeof vis);
	memset(c, 0, sizeof c);
	priority_queue<pli, vector<pli>, greater<pli>> Q;
	dist[S] = 0; Q.push({dist[S], S});
	while(!Q.empty()) {
		int u = Q.top().second; Q.pop();
		if(vis[u]) continue; vis[u] = true;
		for(int i = h[u]; i != -1; i = ne[i]) { int v = e[i];
			if(dist[v] > dist[u] + w[i]) {
				c[v] = 1, wh[v] = i, pre[v] = u; dist[v] = dist[u] + w[i];
				Q.push({dist[v], v});
			} else if(dist[v] == dist[u] + w[i]) c[v] += wh[v] != i, pre[v] = min(pre[v], u);
		}
	}
	for(int i = 2; i <= n; i ++) 
		if(c[i] < 2 && pre[i] != 1) return false;
	return true;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	memset(h, -1, sizeof h);

	cin >> n >> m;
	for(int i = 0; i < m; i ++) {
		int a, b, c; cin >> a >> b >> c;
		add(a, b, c), add(b, a, c);
	}
	cout << (Dijkstra() ? "YES" : "NO") << '\n';

	return 0;
}

D

题目

现在 Y 国从仓库里取出了 \(n\) 个机甲,第 \(i\) 个机甲版本号为 \(i\)。仓库里还有 \(m\) 个武器,第 \(i\) 个武器攻击力为 \(a_i\)
武器的驱动、接口、协议会随着机甲版本的更新而改变,所以,对于一个武器而言,太老或者太新版本的机甲都不能装配。具体的,对于第 \(i\) 个武器,它只能给版本号在 \([l_i,r_i]\) 内的机甲装配。一个机甲至多只能装配一个武器。
现在,你需要帮助 Y 国分配武器,使得尽可能多的机甲装配了武器。在此基础上,你还需要使得被装配的武器的攻击力和最大。

概括

给定左侧有 \(n\) 个点,右侧有\(m\) 个点,右侧第$ i$ 个点点权为 \(a_i\)。左侧编号在$[li, ri] \(里的点和右侧第\) i$ 个点有连边。求最大匹配并在此基础上求最大权匹配。
\(1 ≤ n ≤ 500, 1 ≤ m ≤ 106, 1 ≤ li ≤ ri ≤ n, 1 ≤ ai ≤ 109。\)

代码

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 510, maxm = 1e6 + 10;
int n, m;
bool vis[maxn]; int s_vis[maxn];
int Lmatch[maxn], Rmatch[maxm], pre[maxm];

struct node {
	int l, r, a;
	inline void input() {cin >> l >> r >> a; }
	bool operator < (const node &rhs) const {return a > rhs.a; }
} A[maxm];

vector<int> vec;
inline void build() {
	memset(vis + 1, false, sizeof(bool) * n);
	queue<int> Q;
	for(int i = 1; i <= n; i ++) 
		if(!Lmatch[i]) vis[i] = true, pre[i] = 0, Q.push(i);
	while(!Q.empty()) {
		int u = Q.front(); Q.pop();
		for(int i = 0; i < vec.size(); i ++) if(u >= A[vec[i]].l && u <= A[vec[i]].r) {
			if(!vis[Rmatch[i + 1]]) vis[Rmatch[i + 1]] = true, pre[Rmatch[i + 1]] = u, Q.push(Rmatch[i + 1]);
		}
	}
	for(int i = 1; i <= n; i ++)
		s_vis[i] = s_vis[i - 1] + vis[i];
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);

	cin >> n >> m;
	memset(Lmatch + 1, 0, sizeof(int) * n), memset(Rmatch + 1, 0, sizeof(int) * m);
	for(int i = 1; i <= m; i ++)
		A[i].input();
	sort(A + 1, A + m + 1);

	long long ans = 0; build();
	for(int i = 1; i <= m && vec.size() < n; i ++) {
		int l = A[i].l, r = A[i].r, w = A[i].a;
		if(!(s_vis[r] - s_vis[l - 1])) continue;
		
		ans += w, vec.emplace_back(i);
		int u = 0, lst = vec.size();
		for(int j = l; j <= r; j ++)
			if(vis[j]) {u = j; break; }
		while(u) {
			Rmatch[lst] = u; swap(Lmatch[u], lst);
			u = pre[u];
		}
		build();
	}
	cout << vec.size() << ' ' << ans << '\n';

	return 0;
}

Day4

0+0+0+0=0

评分:D---------

排名:50/58

A

原题面

A传送门

题目概括

给定一个 \(n\) 个点的有根树,询问有几个点的子树大小超过整个树的一半。
\(1 \le n \le 3 \times 105\)

思路

很简单就是DFS,然后直接模拟,结束,时间复杂度 \(O(n)\)

代码

神奇的code
#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5 + 10;
int n, siz[maxn], fa[maxn], d[maxn];

int main() {

	cin >> n;
	for(int i = 2; i <= n; i ++) {
		cin >> fa[i]; d[fa[i]] ++;
	}

	queue<int> Q;
	for(int i = 1; i <= n; i ++)
		if(!d[i]) Q.push(i);
	for(int i = 1; i <= n; i ++) siz[i] = 1;
	while(!Q.empty()) {
		int u = Q.front(); Q.pop();
		if(u == 1) continue;
		siz[fa[u]] += siz[u];
		if(--d[fa[u]] == 0) Q.push(fa[u]);
	}
	int ans = 0;
	for(int i = 1; i <= n; i ++)
		ans += (2 * (siz[i] - 1) >= n);
	cout << ans << '\n';

	return 0;
}

B

原题面

B传送门

题目概括

给定 $[1, n] \cap Z $ 的一个子集 S,询问若 \(i\) 的父亲在 \([l,i-1]\) 中等概率生成,
\(S\) 中的点从上到下形成一条链的概率,q 次询问。

\(1 \le n, q \le 3 \times 105\),询问的子集大小总和不超过 \(3 \times 105\)

思路

此题有坑

假设 \(S\) 中点从小到大排序,依次为 \(x_1,x_2,x_3 \dots x_m\)

\(x_i\)\(x_{i+1}\) 的祖先"这些事件之间是互相独立的。

然后通过计算,发现 \(x_i\)\(x_{i+1}\) 的祖先的概率和 \(x_{i+1}\) 具体是什么无关:

我们令 \(x_{i+1}\) 一直跳父亲,直到编号小于等于 \(x_i\) 的那一次。因为父亲是等概率去选取的,所以概率就是 \(\frac{1}{x_i}\)

同时,对于原题答案即为 \(\prod_{i=1}^{m-1}\frac{1}{x_i}\)

总复杂度 \(O(n +\sum k_i)\)

代码

神奇的code
#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5 + 10, maxm = 6e5 + 10, MOD = 998244353;
int n, q;

inline long long ksm(long long a, int b) {
	long long r = 1;
	while(b) {
		if(b & 1) r = r * a % MOD;
		b >>= 1, a = a * a % MOD;
	}
	return r;
}

long long fac[maxn], inv[maxn];
inline void init() {
	fac[0] = 1;
	for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % MOD;
	inv[n] = ksm(fac[n], MOD - 2);
	for(int i = n - 1; i >= 0; i --) inv[i] = inv[i + 1] * (i + 1) % MOD;
}
long long f[maxn];

int main() {

	cin >> n >> q;
	init();

	static vector<int> vec;
	while(q --) { vec.clear();
		int k; cin >> k; vec.resize(k);
		bool flag = false;
		for(int i = 0; i < k; i ++) {
			cin >> vec[i]; vec[i] --;
			flag = flag || (vec[i] == 0);
		}
		if(!flag) vec.emplace_back(0);
		sort(vec.begin(), vec.end());

		f[0] = 1;
		for(int j = 1; j < vec.size(); j ++)
			f[j] = f[j - 1] * (fac[vec[j]] * inv[vec[j - 1] + 1] % MOD) % MOD;

		cout << f[vec.size() - 1] * inv[vec.back()] % MOD << '\n';
	}

	return 0;
}

C

原题面

C传送门

题目概括

给定一个\(n\) 个点 \(m\) 条边的无向简单图,两条边可以匹配当且仅当它们有

共用的端点,求是否有完美匹配,报告无解或构造。
\(1 \le n, m \le 3 \times 105\)

思路

猜测有解当且仅当每个连通块边数都是偶数,下面构造进行证明。

对于部分分的特殊结构,树可以叶子开始构造,仙人掌可以建圆方树然后

和树类似的构造,海胆图找到中心后就很容易构造了。

对于一般的图而言,我们建立 dfs 树,这样非树边只有返祖边。从深到浅

考察每个点,考虑这个点与儿子的连边及其连出的非树边中还没有匹配的边的

数量,如果为偶数则直接两两匹配,否则再加入这个点与父亲的连边然后两两

匹配。由于除了根节点之外每个点都能用和父亲的连边调整奇偶性,且连通块

一共有偶数条边,所以一定能完美匹配。
复杂度 \(O(n+m)\)

代码

点击查看代码
#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> edge;

const int maxn = 3e5 + 10, maxm = 6e5 + 10;
int n, m; edge E[maxm];
vector<int> G[maxn];

int dfn[maxn], dfs_clock, fa[maxn], ans[maxn];
int c[maxn];
bool dfs(int u, int f) {
	dfn[u] = ++dfs_clock, fa[u] = f;
	for(auto i : G[u]) {
		if(i == f) continue;
		int v = E[i].first + E[i].second - u;
		if(!dfn[v]) {
			if(dfs(v, i)) ans[i] = u, c[u] ^= 1;
		} else if(!ans[i]) ans[i] = u, c[u] ^= 1;
	}
	if(f == -1 && c[u]) return false;
	if(c[u]) {ans[f] = u; return false; }
	return true;
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);

	cin >> n >> m;
	for(int i = 1; i <= m; i ++) {
		int u, v; cin >> u >> v; E[i] = {u, v};
		G[u].emplace_back(i), G[v].emplace_back(i);
	}

	bool flag = true;
	for(int i = 1; i <= n; i ++) if(!dfn[i]) {
		flag = flag && dfs(i, -1);
	}
	if(flag) {
		cout << "YES\n";
		for(int i = 1; i <= m; i ++) cout << ans[i] << '\n';
	} else cout << "NO\n";

	return 0;
}

D

原题面

D传送门

题目概括

给定 \(n\)\(m\) 个区间 \([li, ri] \subset [1, n]\) 以及一个长度为 \(n\) 的正整数序列
\(a_1 \dots n\) 你可以给 \(1\)\(n\) 中至多 \(k\) 个点打标记,使得

\[\sum_{i=1}^{n}[\forall i\in [l_j,r_j],\exists r_j\ge p\ge i,c_p=1 ]a_i \]

最大,其中 \(c_i = 1/0\)\(i\) 是否被打了标记。

\(1 \le n\),$ m \le 3 \times 105$, \(0 \le k \le n\)

思路

\(R_i = min_{i \in [l_j ,r_j ]}{r_j}\)

那么 \(i\) 可以贡献当且仅当 \([i, R_i]\) 中存在至少一个点被标记。直接暴力 dp

可以做到 \(O(n^2k)\)
注意到如果 \(j \in [i, Ri]\),那么 \(R_j \le R_i\),所以 \([i, Ri]\) 这些区

间之间要么无交,那么互相包含。根据区间包含关系建树,那么我们每次标记可以

保证一条到根的链不被破坏。

在树上 dp 的话,使用树上背包的技巧可以做到 \(O(nk)\)

但是并不需要这么麻烦,我们考虑将树长剖,那么其实我们的答案就是前 \(k\) 条长链的带权长度和。
总复杂度 \(O(n + m)\)

神奇的code
#include <bits/stdc++.h>

using namespace std;

const int maxn = 3e5 + 10, maxm = 6e5 + 10;
int n, m, k, A[maxn], L[maxn], R[maxn];

int h[maxn], e[maxm], ne[maxm], w[maxm], idx;
inline void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++; }

int fa[maxn], hson[maxn], top[maxn]; long long height[maxn], val[maxn];
void dfs(int u, int f) {
	fa[u] = f;
	for(int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i]; if(v == f) continue;
		dfs(v, u); 
		if(height[v] + w[i] > height[u]) {
			height[u] = height[v] + w[i]; hson[u] = v;
		}
	}
}
void hld(int u, int topf) {
	val[topf] += A[u];
	if(!hson[u]) return;
	hld(hson[u], topf);
	for(int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i]; if(v == fa[u] || v == hson[u]) continue;
		hld(v, v);
	}
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	memset(h, -1, sizeof h);

	cin >> n >> m >> k;
	for(int i = 1; i <= n; i ++) cin >> A[i];
	for(int i = 1; i <= n; i ++) L[i] = n + 1;
	for(int i = 1; i <= m; i ++) {
		int l, r; cin >> l >> r;
		L[r] = min(L[r], l);
	}

	{
		set<int> Set;
		for(int i = 1; i <= n; i ++) Set.insert(i);
		for(int r = 1; r <= n; r ++) {
			int l = L[r]; if(l == n + 1) continue;
			auto it = Set.lower_bound(l);
			while(it != Set.end() && *it <= r) {R[*it] = r; it = Set.erase(it); }
		}
	}
	long long ans = 0;
	for(int i = 1; i <= n; i ++) 
		if(R[i]) ans += A[i];

	{
		set<pair<int, int>> Set;
		vector<int> ind(n + 1, 0);
		for(int i = 1; i <= n; i ++) if(R[i]) {
			auto it = Set.lower_bound({R[i], -n - 1});
			if(it != Set.end()) add(-it->second, i, A[i]), ind[i] ++;
			Set.insert({R[i], -i}); 
		}
		for(int i = 1; i <= n; i ++) 
			if(R[i] && !ind[i]) add(0, i, A[i]);
	}
	dfs(0, -1), hld(0, 0);
	sort(val, val + n + 1, greater<long long>());
	for(int i = 0; i < k; i ++) ans -= val[i];
	cout << ans << '\n';

	return 0;
}

Day5

0+50+60+0=110

评分:C

rk36/58

T1

原题面

T1传送门

题目(没权限的看这个)

查看题目
![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728190337480-789319674.png)

![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728190357922-728216462.png)

思路

直接按照题目模拟,设 \(f_{i, j, k, 0/1}\) 表示从 \((1, 1)\) 走到 \((i, j)\),异或和为 \(k\),且没有扔掉/扔掉一个珍珠的方案数。

每次直接枚举 \((i, j, k)\) 是向下走还是向右走就行。

时间复杂度 \(O(nma)\)

代码

神奇的code
#include <bits/stdc++.h>

using namespace std;

const int N = 305, M = 128, MOD = 998244353;
int n, m, A[N][N];
int f[N][N][M + 5], g[N][N][M + 5];
int p1[N][N][8], q1[N][N][8], p0[N][N][8], q0[N][N][8];

inline int add(int x, int y) {return (x + y > MOD ? x + y - MOD : x + y); }

int main() {
	ios::sync_with_stdio(false), cin.tie(0);

	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
			cin >> A[i][j];


	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++) {
			if(i == 1 && j == 1) {f[i][j][A[i][j]] = 1; continue; }
			for(int k = 0; k < M; k ++)
				f[i][j][k xor A[i][j]] = add(f[i - 1][j][k], f[i][j - 1][k]);
		}
	for(int i = n; i >= 1; i --)
		for(int j = n; j >= 1; j --) {
			if(i == n && j == n) {g[i][j][A[i][j]] = 1; continue; }
			for(int k = 0; k < M; k ++)
				g[i][j][k xor A[i][j]] = add(g[i + 1][j][k], g[i][j + 1][k]);
		}

	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++) {
			for(int k = 0; k < M; k ++)
				for(int l = 0; l < 8; l ++) {
					if(k >> l & 1) {
						p1[i][j][l] = (p1[i][j][l] + f[i][j][k]) % MOD;
						q1[i][j][l] = (q1[i][j][l] + g[i][j][k]) % MOD;
					} else {
						p0[i][j][l] = (p0[i][j][l] + f[i][j][k]) % MOD;
						q0[i][j][l] = (q0[i][j][l] + g[i][j][k]) % MOD;
					}
				}
		}

	long long ans = 0;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++) {
			for(int k = 0; k < 8; k ++)
				ans = (ans + (1ll << k) * (1ll * p1[i][j][k] * q0[i][j][k] % MOD + 1ll * p0[i][j][k] * q1[i][j][k] % MOD) % MOD) % MOD;
		}
	cout << ans << '\n';

	return 0;
}

T2

原题面

T2传送门

题目

点击查看题目
![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728190704386-1162337991.png)

![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728190724476-312307280.png)

思路

考虑将一条合法的路径在 LCA 处计算进答案。可以设 \(f_{i, j}\) 表示 \(i\) 子树内一条祖先-儿子链,且祖先>儿子,最后一个点是 \(j\) 的方案数,\(g_{i, j}\) 则是祖先<儿子,最后一个点是 \(j\) 的方案数。

\(u\) 合并一颗子树 \(v\) 的时候,答案会增加 \(\sum\limits_{a < b} f_{u, a} \times g_{v, b} + f_{u, b} \times g_{v, a}\)

时间复杂度 \(O(n^2)\)

代码

神奇的code
#include <bits/stdc++.h>

using namespace std;

const int N = 5e3 + 5, M = 1e4 + 5, MOD = 1e9 + 7;
int n, h[N], e[M], ne[M], idx;
inline void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++; }
inline int Add(int a, int b) {return (a + b > MOD ? a + b - MOD : a + b); }

long long ans = 0, f[N][N], g[N][N];
long long G[N][N];
void dfs(int u, int fa) {
	f[u][u] = g[u][u] = 1;
	for(int j = n; j >= 1; j --)
		G[u][j] = Add(G[u][j + 1], g[u][j]); 
	for(int i = h[u]; i != -1; i = ne[i]) {
		int v = e[i]; if(v == fa) continue;
		dfs(v, u); 
		for(int j = 1; j <= n; j ++) {
			ans = Add(ans, f[u][j] * G[v][j] % MOD);
			ans = Add(ans, f[v][j] * G[u][j] % MOD);
		}
		for(int j = 1; j <= n; j ++) {
			f[u][j] = Add(f[u][j], f[v][j]);
			g[u][j] = Add(g[u][j], g[v][j]);
		}
		for(int j = 1; j < u; j ++) 
			f[u][u] = Add(f[u][u], f[v][j]);
		for(int j = u + 1; j <= n; j ++)
			g[u][u] = Add(g[u][u], g[v][j]);
		for(int j = n; j >= 1; j --)
			G[u][j] = Add(G[u][j + 1], g[u][j]); 
	}
}

int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	memset(h, -1, sizeof h);

	cin >> n;
	for(int i = 1; i < n; i ++) {
		int a, b; cin >> a >> b;
		add(a, b), add(b, a);
	}
	dfs(1, 0);
	cout << ans << '\n';

	return 0;
}

T3

原题面

T3传送门

题目

点击查看题目
![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728191018548-389100424.png)

![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728191026864-1288994783.png)

思路

分别要记 \(f_{i, j}\) 表示所有数确定完低 \(i\) 位,且低 \(i\) 位的和等于 \(n\) 的低 \(i\) 位的方案数,\(g_{i, j}\) 表示所有合法方案中的异或和。

转移的话,\(f_{i, j}\) 直接枚举第 \(i + 1\) 位有几个 \(1\),设为 \(x\),然后只要 \(x + j\) 的奇偶性和 \(n\)\(i + 1\) 位一样就行。

\(g_{i, j}\) 有两个部分的贡献:一个是低于 \(i + 1\) 位的贡献,一个是第 \(i + 1\) 位的贡献。分别算即可。即 \(g_{i, j} \times \binom{n}{x} + 2^{i + 1} \times f_{i, j} \times \binom{n}{x}\)

时间复杂度 \(O(n^2 \log m)\)

代码

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int N = 1e3 + 10, B = __lg((int)1e9) + 5, MOD = 1e9 + 9;
int n, m, b;
long long f[B][2 * N], g[B][2 * N];
inline long long ksm(long long a, int b) {
	long long r = 1;
	while(b) {
		if(b & 1) r = r * a % MOD;
		b >>= 1, a = a * a % MOD;
	}
	return r;
}

long long fac[N], inv[N];
inline void init() {
	fac[0] = 1;
	for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % MOD;
	inv[n] = ksm(fac[n], MOD - 2);
	for(int i = n - 1; i >= 0; i --) inv[i] = inv[i + 1] * (i + 1) % MOD;
}
inline long long C(int n, int m) {return n >= m ? fac[n] * inv[n - m] % MOD * inv[m] % MOD : 0; }

bool vis[B][2 * N];

int main() {
	ios::sync_with_stdio(false), cin.tie(0);

	cin >> n >> m; b = __lg(m); int c = 2 * n; init();

	f[0][0] = 0; g[0][0] = 1; vis[0][0] = true;
	for(int i = 1; i <= b + 1; i ++) {
		for(int j = 0; j <= c; j ++) for(int k = 0; k <= n; k ++) if((j + k & 1) == (m >> i - 1 & 1) && vis[i - 1][j]) {
			f[i][(j + k) >> 1] = (f[i][(j + k) >> 1] + f[i - 1][j] * C(n, k) % MOD) % MOD;
			if(k & 1) f[i][(j + k) >> 1] = (f[i][(j + k) >> 1] + (1 << i - 1) * g[i - 1][j] % MOD * C(n, k) % MOD) % MOD;
			g[i][(j + k) >> 1] = (g[i][(j + k) >> 1] + g[i - 1][j] * C(n, k) % MOD) % MOD;
			vis[i][(j + k) >> 1] = true;
		}
	}
	cout << f[b + 1][0] << '\n';

	return 0;
}

T4

原题面

T4传送门

题目

神奇的code
![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728191405324-16333520.png)

![image](https://img2023.cnblogs.com/blog/3249466/202307/3249466-20230728191418697-146686091.png)

思路

首先,假设当我们已经选好了每个点的标准,那么每个人就一定会贪心地去他所能到达的店中标准最高的。

可以枚举这个区间的最大值是什么,即设 \(f_{i, j}\) 表示只考虑区间 \([i, j]\) 里的人的最大收益是什么,然后枚举标准最高的点 \(k\),设能到达 \(k\) 的人有 \(t\) 个,那么就是 \(f_{i, k - 1} + f_{j, k + 1} + g_{k, t}\),其中 \(g_{k, t}\) 表示有 \(t\) 个人到第 \(k\) 家店,合理选择标准能达到的最高收益是什么。

那么接下来就是求出所有 \(g_{k, t}\)。由于每个人都求这个(过程一样),于是下面用 \(c_i\) 代表标准为 \(i\) 的花费,\(f_j\) 表示有 \(j\) 个人来,那么最高收益是什么。

显然 \(f_j = \max i \times j - c_i\)。这个是一个标准的斜率优化式子,建出凸包来即可。

但是斜率优化是有限制的,比如这些都是能放在平面直角坐标系上面的直线(也就是说贡献都可以写成 \(kx + b\))的形式。

下面给一种感觉通用性更高,且理解起来并不比斜率优化复杂的决策单调性优化方法(虽然写起来其实本质相同)。

对于一个 \(j\),假设 \(i_1 < i_2\),且这个时候选 \(i_2\)\(i_1\) 更加优秀,那么对于所有 \(k > j\)\(i_2\) 都会比 \(i_1\) 优秀,那么 \(i_1\) 在这之后就没有用了。

那么我们按 \(i\) 从小到大的顺序加入 \(i \times j - c_i\) 这条直线。假设已经处理完了之前的所有直线,那么对于所有可能成为最优选择的 \(i_1 < i_2 < \cdots i_k\),一定有 \(i_x\)\(i_{x - 1}\) 后面一些点的最优解。

我们对每个 \(i_x\) 记录 \(p_x\) 表示对于 \(j < p_x\)\(i_x\)\(i_{x + 1}\) 优,之后则是 \(i_{x + 1}\) 优,那么显然 \(p_1 < p_2 < \cdots p_{k - 1}\)

否则若 \(p_x > p_{x + 1}\),那么在 \(i_{x + 1}\)\(i_x\) 优之前,\(i_{x + 2}\) 就已经比 \(i_{x + 1}\) 优了,所以 \(i_{x + 1}\) 就不可能成为最优选择了。

那么加入 \(i\) 的时候,先算出来 \(i\)\(i_k\) 什么时候优(因为 \(i > i_k\),所以一定有个时间更优),设为 \(y\),那么如果 \(x_{k - 1} > y\),那么根据上面 \(p_x\) 单调的解释,就需要删除 \(i_k\),那么这样做就能维护出对于所有 \(j\),什么 \(i\) 最优。

做完这个之后直接枚举 \(j\),然后双指针不断从前往后扫到最后一个 \(p_x < j\)\(x\)\(i_{x + 1}\) 就是最优的答案。

这样的时间复杂度是线性。于是,整个题时间复杂度 \(O(n^3 + nk)\)

代码

神奇的code
#include <bits/stdc++.h>
const int mod=1e9+7;
using namespace std;
int n,m,K,c[310][310],w[310][100010],st[10010],t,k[10010],b[10010];
int f[310][310];
int main(){
	scanf("%d%d%d",&n,&m,&K);
	for(int i=1;i<=n;i++){
		st[t=1]=0;
		for(int j=1;j<=K;j++){
			scanf("%d",&b[j]);
			b[j]=-b[j],k[j]=j;
			while(t>=2&&1ll*(b[st[t-1]]-b[st[t]])*(k[j]-k[st[t]])>=1ll*(b[st[t]]-b[j])*(k[st[t]]-k[st[t-1]])) 
			t--;
			st[++t]=j;
		}
		for(int j=1,l=0;j<=m;j++){
			while(l<t&&k[st[l]]*j+b[st[l]]<=k[st[l+1]]*j+b[st[l+1]])
			l++;
			w[i][j]=k[st[l]]*j+b[st[l]];
		}
	}
	for(int i=1,l,r;i<=m;i++) 
		scanf("%d%d",&l,&r),c[l][r]++;
	for(int i=n;i>=1;i--) 
		for(int j=i;j<=n;j++) 
			c[i][j]+=c[i+1][j];
	for(int i=1;i<=n;i++) 
		for(int j=i;j<=n;j++) 
			c[i][j]+=c[i][j-1];
	for(int i=1;i<=n;i++) 
		for(int l=1;l+i-1<=n;l++){
			int r=l+i-1;
				for(int k=l;k<=r;k++) f[l][r]=max(f[l][r],f[l][k-1]+f[k+1][r]+w[k][c[l][r]-c[l][k-1]-c[k+1][r]]);
	}
					printf("%d\n",f[1][n]);
}

Day6

T1

先将读入进来的质数标记为危险的,然后做一遍线性筛,当 \(i \times p_j\) 被标记为非质数时,判断一下 \(i \times p_j\) 是不是危险的(也就是 \(i\)\(p_j\) 是危险的)就行。

时间复杂度 \(O(n)\)

点击查看代码

T2

直接对每个点它的父亲,由于每个数质因子总数 \(\le \log n\),所以每次询问可以暴力跳 LCA。

时间复杂度 \(O(n + q \log n)\)

T3

首先另外一个问题:有多少对 \((x, y)\) 满足 \(1 \le x \le y \le n\)\(y\)\(x\) 的倍数?

可以枚举 \(x\),那么 \(n\) 以内 \(x\) 的倍数就是 \(\lfloor \frac{n}{x} \rfloor\),所以答案就是 \(\sum\limits_{x = 1}^n \lfloor \frac{n}{x} \rfloor\)

回到原问题:设 \(a = y - x, b = y + x\),那么 \(x = \frac{b - a}{2}, y = \frac{b + a}{2}\)。由于 \(y \le n\),所以 \(a + b \le 2n\),且 \(a < b, a, b\) 奇偶性要相同。

所以枚举 \(a\),如果 \(a\) 是偶数,那么 \(b \le 2n - a\)\(b\)\(a\) 的倍数的话,\(b\) 也一定是偶数。\(b\) 的个数就是 \(\lfloor \frac{2n - a}{a} \rfloor - 1\),减一是因为 \(b\) 不能等于 \(a\)

\(a\) 是奇数,那么刚刚那样算出来的 \(b\) 就有可能是偶数,即 \(b\)\(2a\) 的倍数,那么减去 \(\lfloor \frac{n - a}{2a} \rfloor\) 就行。

时间复杂度 \(O(n)\)

T4

首先,如果一个长为 \(k\) 的序列的最小公倍数是 \(i\),那么这个序列的所有元素都是 \(i\) 的约数。设 \(i\) 的约数有 \(d(i)\) 个。

\(k\)\(i\) 的约数的方案是 \(g_i = d(i)^k\)。但如果要求最小公倍数是 \(i\),则这样任意填可能导致最小公倍数不是 \(i\),如:\(i = 4, k = 3\),填的序列是 \((1, 2, 1)\),那么最小公倍数是 \(2\),而不是 \(4\)

设最小公倍数是 \(i\) 的序列个数是 \(f_i\),那么可以先求出 \(g_i\)(任意填 \(i\) 的约数),再减去最终最小公倍数不是 \(i\) 的方案数,那么最小公倍数不是 \(i\),就一定是 \(i\) 的某一个约数 \(j\),且最小公倍数是 \(j\) 的数组所有元素一定是 \(i\) 的约数,也就是它一定会在 \(g_i\) 中被算进去。

所以 \(f_i = g_i - \sum\limits_{j | i, j \neq i} f_j\)

这样暴力递推的复杂度是 \(O(n \log n)\) 的,总时间复杂度 \(O(n \log n + n \log k)\)

Day6

100+50+30+0=180

排名:38/58

评分:C

T1

题目

现在有 \(m\) 个质数是危险的。如果对于某个数 \(x\),它包含了这 \(m\) 个质数中的任何一个作为约数,那么就称 \(x\) 也是危险的。现在给定 \(n\)\(m\) 个质数 \(p_1, p_2, \cdots, p_m\),问 \([1, n]\) 中有多少数是危险的。

第一行输入两个整数 \(n,m\)
第二行输入 \(m\) 个质数 \(p_i\),保证 \(p_i\) 两两不同。

输出一行一个整数,表示有多少个在 \([1, n]\) 中的整数是危险的。

思路

先将读入进来的质数标记为危险的,然后做一遍线性筛,当 \(i \times p_j\) 被标记为非质数时,判断一下 \(i \times p_j\) 是不是危险的(也就是 \(i\)\(p_j\) 是危险的)就行。

时间复杂度 \(O(n)\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool is_prime[30000005],check[30000005];
int prime[4000005],n,m,a,cnt; 
long long ans=0;
void do_prime(){
	for(int i=2;i<=n;i++){
		if(is_prime[i]==false){
			if(check[i])ans++;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt&&prime[j]*i<=n;j++){
			is_prime[prime[j]*i]=true;
			if(check[prime[j]]||check[i]){
				ans++;
				check[prime[j]*i]=1;
			}
			if(i%prime[j]==0)break; 
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a;
		check[a]=1;
	}
	do_prime();
	cout<<ans<<"\n";
	return 0;
}

T2

题目

有一棵 \(n\) 个点的树,对于 \(i > 1\)\(i\)\(\frac{i}{f(i)}\) 间有一条边相连,其中 \(f(i)\) 表示 \(i\) 的最大质因子。
现在有 \(q\) 次询问,每次询问给定 \(x, y\),问树上 \(x\)\(y\) 的路径长度(即要经过多少条边)。

思路

直接对每个点它的父亲,由于每个数质因子总数 \(\le \log n\),所以每次询问可以暴力跳 LCA。

时间复杂度 \(O(n + q \log n)\)

代码

点击查看代码

#include<bits/stdc++.h>
using namespace std;
inline int rd(){
	int res=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=res*10+(ch-'0');
		ch=getchar();
	}
	return res*f;
}
const int N=1e7+10;
bool np[N];
vector<int> prime;
int n,m,bas[N],num[N];
void shai(int n=1e7){
	bas[1]=1;
	for(int i=2;i<=n;i++){
		if(!np[i]){
			prime.push_back(i);
			bas[i]=i;
			num[i]=1;
		}
		for(int j:prime){
			if(i*j>n)break;
			np[i*j]=1;
			num[i*j]=num[i]+1;
			bas[i*j]=min(bas[i],j);
			if(i%j==0){
				break;	
			}
		
		}
	}
}
signed main(){
	shai();
	n=rd();m=rd();
	while(m--){
		int x=rd(),y=rd();
		if(x==y){
			puts("0");
			continue;
		}
		int lc=1,tmp1=x,tmp2=y;
		while(bas[tmp1]==bas[tmp2]&&tmp1!=1&&tmp2!=1){
			lc*=bas[tmp1];
			tmp1/=bas[tmp1];
			tmp2/=bas[tmp2];
		}
		printf("%d\n",num[x]+num[y]-num[lc]*2);
	}
	return 0;
}




T3

题目

对于 \(y\),定义 \(f(y)\) 表示有多少 \(x\) 满足 \(1 \le x < y\),且 \(y - x\)\(y + x\) 的约数。
现在给定 \(n\),求 \(\sum\limits_{y = 2}^n f(y)\)

思路

首先另外一个问题:有多少对 \((x, y)\) 满足 \(1 \le x \le y \le n\)\(y\)\(x\) 的倍数?

可以枚举 \(x\),那么 \(n\) 以内 \(x\) 的倍数就是 \(\lfloor \frac{n}{x} \rfloor\),所以答案就是 \(\sum\limits_{x = 1}^n \lfloor \frac{n}{x} \rfloor\)

回到原问题:设 \(a = y - x, b = y + x\),那么 \(x = \frac{b - a}{2}, y = \frac{b + a}{2}\)。由于 \(y \le n\),所以 \(a + b \le 2n\),且 \(a < b, a, b\) 奇偶性要相同。

所以枚举 \(a\),如果 \(a\) 是偶数,那么 \(b \le 2n - a\)\(b\)\(a\) 的倍数的话,\(b\) 也一定是偶数。\(b\) 的个数就是 \(\lfloor \frac{2n - a}{a} \rfloor - 1\),减一是因为 \(b\) 不能等于 \(a\)

\(a\) 是奇数,那么刚刚那样算出来的 \(b\) 就有可能是偶数,即 \(b\)\(2a\) 的倍数,那么减去 \(\lfloor \frac{n - a}{2a} \rfloor\) 就行。

时间复杂度 \(O(n)\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int rd(){
	int res=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=res*10+(ch-'0');
		ch=getchar();
	}
	return res*f;
}
int n,ans;
signed main(){
	n=rd();
	for(int i=2;i<=2*n-1;i++){
		for(int j=1;j;j++){
			int c1=i*j,c2=j;
			if(((c1+c2)&1)||((c1-c2)&1))continue;
			if((c1+c2)/2>n)break;
			ans++;
		}
	}
	cout<<ans;
	return 0;
}


T4

题目

给定 \(n, k\),现在对于每个 \(1 \le i \le n\)\(i\),求有多少长为 \(k\) 的序列 \(b_1, b_2, \cdots, b_k\) 满足 \(b_j \ge 1\) 且这个序列所有元素的最小公倍数是 \(i\),对 \(998244353\) 取模。

思路

首先,如果一个长为 \(k\) 的序列的最小公倍数是 \(i\),那么这个序列的所有元素都是 \(i\) 的约数。设 \(i\) 的约数有 \(d(i)\) 个。

\(k\)\(i\) 的约数的方案是 \(g_i = d(i)^k\)。但如果要求最小公倍数是 \(i\),则这样任意填可能导致最小公倍数不是 \(i\),如:\(i = 4, k = 3\),填的序列是 \((1, 2, 1)\),那么最小公倍数是 \(2\),而不是 \(4\)

设最小公倍数是 \(i\) 的序列个数是 \(f_i\),那么可以先求出 \(g_i\)(任意填 \(i\) 的约数),再减去最终最小公倍数不是 \(i\) 的方案数,那么最小公倍数不是 \(i\),就一定是 \(i\) 的某一个约数 \(j\),且最小公倍数是 \(j\) 的数组所有元素一定是 \(i\) 的约数,也就是它一定会在 \(g_i\) 中被算进去。

所以 \(f_i = g_i - \sum\limits_{j | i, j \neq i} f_j\)

这样暴力递推的复杂度是 \(O(n \log n)\) 的,总时间复杂度 \(O(n \log n + n \log k)\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define fr first
#define sc second 
inline int rd(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=res*10+(ch-'0');
		ch=getchar();
	}
	return res*f;
}
const int N=1e6+10,mod=998244353;
int f[N],n,k;
inline int qpow(int b,int p){
	int res=1;
	while(p){
		if(p&1)res=res*b%mod;
		b=b*b%mod;
		p>>=1;
	}	
	return res;
}
signed main(){
	n=rd();k=rd();
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i){
			f[j]++;
		}
	}
	for(int i=1;i<=n;i++){
		f[i]=qpow(f[i],k);
	}
	for(int i=1;i<=n;i++){
		for(int j=2*i;j<=n;j+=i){
			f[j]=(f[j]-f[i]+mod)%mod;
		}
	}
	for(int i=1;i<=n;i++){
		printf("%lld ",f[i]);
	}
	return 0;
}


posted @ 2023-08-02 19:36  Kylin_ZHH  阅读(113)  评论(0)    收藏  举报