模拟赛2025

模拟赛 2025

10.24 NOIP 模拟赛 十一

100+100+50+100=350 pts,rk1.

比较抽象,又被 T1 创飞了,写了将近 3 小时。

T2 暴力拿满,HHHOJ 机子跑的太快了。

T1

给定一个自然数 \(N(N\le 10^6)\),找出一个 \(M\),使得 \(M > 0\)\(M\)\(N\) 的倍数,并且 \(M\)\(10\) 进制表示只包含 \(0\)\(1\)。求最小的 \(M\)。无解输出 \(-1\)

原题 lg P2841,不过数据加强了一点,但不用输出 \(M \div N\)

bfs,将模 \(N\) 余数相同的剪枝。

据说可以加强到 \(N\le 10^9\),但我不会。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define PSL pair<string,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n;
bool vis[N];
queue<PSL> q;
int main(){
	IOS;cin>>n;
	q.push({"1",1});
	vis[1]=1;
	while(!q.empty()){
		PSL tp=q.front();q.pop();
		if(!(tp.second*10%n)) cout<<tp.first+"0"<<"\n",exit(0);
		if(!((tp.second*10+1)%n)) cout<<tp.first+"1"<<"\n",exit(0);
		if(!vis[tp.second*10%n]){
			q.push({tp.first+"0",tp.second*10%n});
			vis[tp.second*10%n]=1;
		}
		if(!vis[(tp.second*10+1)%n]){
			q.push({tp.first+"1",(tp.second*10+1)%n});
			vis[(tp.second*10+1)%n]=1;
		}
	}
	return 0;
} 


T2

我们定义两个 \(01\) 串的相似值为:如果两个串在第 \(i\) 位是一样的,那么他们的相似值要加上 \(w_i\)
现在给你 \(m\) 个长度为 \(n\)\(01\) 串,然后有 \(q\) 个询问,每一次给定一个长度为 \(n\)\(01\) 串,和一个值 \(k\),求在这 \(m\) 个串中,有多少个串和它的相似值不超过 \(k\)
\(n \le 15,k \le 30,m\le 5\times 10^5\)

\(f_{S,k}\) 表示询问为 \(S\) 时小于等于 \(k\) 的个数,暴力枚举。复杂度 \(\mathcal{O(2^{2n})}\) 居然能跑过。

正解:

题目代价可以转化为:将第 \(i\) 位取反的代价为 \(w_i\),问能在小于等于 \(k\) 的代价将其变成与 \(S\) 完全不同的串的数量。

\(f_{i,S,k}\) 表示第 \(1 \sim i\) 位与 \(S\) 完全不同,第 \(i+1 \sim n\) 位与 \(S\) 完全相同,且代价小于等于 \(k\) 的串的数量。

有转移:

\[f_{i,S,k}= \begin{cases} f_{i-1,S,k},w_i>k \\ f_{i-1,S,k}+f_{i-1,S \setminus i},w_i\le k \end{cases} \]

初始状态 \(f_{0,S,k}=num_S\),其中 \(num_S\) 表示给定的 \(01\) 串为 \(S\) 的个数。

每次询问答案即为 \(f_{n,S,k}\)。复杂度 \(\mathcal{O(2^n n k + q)}\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=5e5+5,M=15+5,P=(1<<15)+5;
const ll INF=1ll<<60,mod=998244353;
int n,m,q,w[M],a[N],b[P],f[2][P][M<<1],num[P];
int main(){
	IOS;cin>>n>>m>>q;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<=m;i++){
		for(int j=0;j<n;j++){
			char ch;cin>>ch;
			if(ch=='1') a[i]|=1<<j;
		}
		num[a[i]]++;
	}
	int U=(1<<n)-1;
	for(int t=0;t<=U;t++){
		for(int j=0;j<=30;j++) f[0][t][j]=num[t];
	}
	for(int i=1;i<=n;i++){
		MS(f[i&1],0);
		for(int t=0;t<=U;t++){
			for(int j=0;j<=30;j++){
				if(j>=w[i]) f[i&1][t][j]=f[(i&1)^1][t][j-w[i]];
				f[i&1][t][j]+=f[(i&1)^1][t^(1<<(i-1))][j];
			}
		}
	}
	for(int i=1,x,k;i<=q;i++){
		x=0;
		for(int j=0;j<n;j++){
			char ch;cin>>ch;
			if(ch=='1') x|=1<<j;
		}
		cin>>k;
		cout<<f[n&1][x][k]<<"\n";
	}
	return 0;
}


T3

咕咕咕。

T4

给你 \(n\) 个数,对于每个数 \(a_i\),判断是否存在一个 \(a_j\),使得 \(a_i \& a_j=0\)\(\&\) 表示按位与。

SOSDP 板子,等价于是否存在 \(a_j \subseteq U\setminus a_i)\),不知为何放 T4。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=(1<<20)+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,a[N];
bool f[N];
int main(){
	IOS;cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],f[a[i]]=1;
	int U=(1<<20)-1;
	for(int t=0;t<=U;t++){
		for(int i=0;i<20;i++){
			if(!((t>>i)&1)) continue;
			f[t]|=f[t^(1<<i)];
		}
	}
	for(int i=1;i<=n;i++) cout<<f[U^a[i]]<<" ";
	cout<<"\n";
	return 0;
}


10.25 NOIP 模拟赛 十二

90+0+0+0=90pts,rk9
被 T1 创飞,导致 T2 想出正解但没写完,糖丸了。

T1

\(n\) 个点,给定 \(a_i,b_i (2 \le i \le n)\),表示 \(i\)\([a_i,i]\) 连权值为 \(1\) 的边,\([b_i,i]\)\(i\) 连权值为 \(1\) 的边。
\(dis_{i,j}\) 表示 \(i\)\(j\) 的最短路,求所有的 \(dis_{i,j}(1\le i,j \le n)\)。答案输出 \(dis_{i,j} \times (i+j)\) 的异或和。
\(n\le 6000\)

赛时想了一个假的做法,但拿了高达 90pts。

正解:

考虑枚举出发点 \(s\) ,设 \(f_i\) 表示 \(s\)\(i\) 的最短路。有 \(f_s=0\)

对于 \(i>s\),会发现最短路肯定是 \(s\) 向右一直跳到一个 \(j\),再向左跳若干步到达 \(i\)(可以不往左挑)。其他情况不是最优的。

对于 \(i<s\),最短路是由 \(s\) 一直向左跳到 \(i\);或是先向左跳到 \(j(i < j \le s)\),再跳到 \(k(k>s)\),最后跳到 \(i\)

计算可以设 \(pos_w\) 表示所有的 \(i\) 满足 \(f_i = w\),它最小往左能跳到的位置。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
#define int ll
using namespace std;
const int N=6e3+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,a[N],b[N],pos[N],f[N],ans;
void solve(int s){
	MS(pos,0);MS(f,0x3f);
	f[s]=0;pos[0]=s;
	for(int i=s+1,x=0;i<=n;i++){//往右跳
		x++;
		for(;x && pos[x-1]>=b[i];x--);
		pos[f[i]=x+1]=i;
	}
	MS(pos,0x3f);
	for(int i=n,x=INF;i;i--){//从 i 右边某个点跳过来
		x=min(x+1,f[i]);
		for(;x && pos[x-1]<=i;x--);
		f[i]=min(f[i],x+1);
		pos[f[i]]=min(pos[f[i]],a[i]);
	}
	for(int i=1;i<=n;i++) ans^=f[i]*(i+s);
}
signed main(){
	IOS;cin>>n;
	for(int i=2;i<=n;i++) cin>>a[i];
	for(int i=2;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++) solve(i);
	cout<<ans<<"\n";
	return 0;
}

T2

有⼀棵 \(n\) 个节点的树,每条边⻓度为 \(1\),颜⾊为⿊或⽩。可以执⾏若⼲次如下操作:

  • 选择⼀条简单路径,反转路径上所有边的颜⾊。

对于某些边,要求在操作结束时为某⼀种颜⾊。给定每条边的初始颜⾊,求最⼩操作数,以及满⾜操作数最⼩时,最⼩的操作路径⻓度和。

可通过将目标颜色和当前颜色的转化,将问题转化为:给定若干个点的颜色,其余点的颜色可以任意取,求答案。

树形 dp,设 \(f_{i,0/1}\) 表示 \(i\) 到它父亲的这条边为 \(0/1\) 这种颜色的最优答案。

可以将与 \(i\) 这个点连的边合并成一条路径,两个单独的路径合并可以使操作次数减 \(1\)。分别考虑奇数条边与偶数条边的转移即可。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1e9,mod=998244353;
int n;
PII f[N][2];
vector<PII> to[N];
PII operator + (const PII xx,const PII yy){
	return {xx.first+yy.first,xx.second+yy.second};
}
void dfs(int u,int fa,int fc){
	PII q={0,0},p={INF,INF};//q 0,p 1
	if(fc==1) f[u][0]={INF,INF};
	if(fc==0) f[u][1]={INF,INF};
	for(PII ttp:to[u]){
		int v=ttp.first,c=ttp.second;
		if(v==fa) continue;
		dfs(v,u,c);
		PII tp=p,tq=q;
		p=min(tp+f[v][0],tq+f[v][1]);
		q=min(tp+f[v][1],tq+f[v][0]);
	}
	if(fc==2 || fc==1) f[u][1]=min(PII{q.first+1,q.second+1},PII{p.first,p.second+1});
	if(fc==2 || fc==0) f[u][0]=min(PII{p.first+1,p.second},q);
}
int main(){
	IOS;cin>>n;
	for(int i=1,u,v,c,d;i<n;i++){
		cin>>u>>v>>c>>d;
		c^=(d==1);//转换条件
		if(d==2) c=2;
		to[u].eb(v,c);
		to[v].eb(u,c);
	}
	dfs(1,0,2);
	cout<<f[1][0].first/2<<" "<<f[1][0].second<<"\n";
	return 0;
}


T3

咕咕咕。

T4

给定⻓度为 \(n\) 的正整数序列 ,以及 \(q\) 个操作。每个操作是以下 \(2\) 种之⼀:

  • \(1~~pos~~val\):将 \(a_{pos}\) 改为 \(val\)
  • \(2~~l~~r\): 询问如果从 \(a_{l,\dots,r}\) 中选择三个元素作为三⻆形三条边的⻓度,能够围出的三⻆形的最⼤周⻓是多少。请注意,每个元素最多被选⼀次。

暴力做法为将 \(a_{l,\dots,r}\) 部分从大到小排序后找到第一个 \(i\) 满足 \(a_i+a_{i+1}>=a_{i+2}\) 即为答案。

会发现不存在答案的情况的序列最长为斐波那契数列,最长只有 \(60\) 左右。因此只用保留前 \(60\) 个数即可。用线段树维护。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,q,a[N];
struct Tree{
	int l,r,cnt[66],len;
	#define l(p) t[p].l
	#define r(p) t[p].r
	#define cnt(p,x) t[p].cnt[x]
	#define len(p) t[p].len
}t[N<<2];
int Cnt[66],Len,b[66];
void out(int p){
	for(int i=1;i<=len(p);i++) cout<<cnt(p,i)<<" ";cout<<"\n";
}
void up(int p){
	len(p)=1;
	for(int l1=1,l2=1;len(p)<=60 && (l1<=len(p<<1) || l2<=len(p<<1|1));len(p)++){
		if(cnt(p<<1,l1)>=cnt(p<<1|1,l2)) cnt(p,len(p))=cnt(p<<1,l1++);
		else cnt(p,len(p))=cnt(p<<1|1,l2++);
	}
	len(p)--;
}
void merge(int p){
	int LL=1;MS(b,0);
	for(int l1=1,l2=1;LL<=60 && (l1<=Len || l2<=len(p));LL++){
		if(Cnt[l1]>=cnt(p,l2)) b[LL]=Cnt[l1++];
		else b[LL]=cnt(p,l2++);
	}
	Len=LL-1;MC(Cnt,b);
}
void Build(int p,int l,int r){
	l(p)=l,r(p)=r;
	if(l==r) return cnt(p,len(p)=1)=a[l],void();
	int mid=(l+r)>>1;
	Build(p<<1,l,mid);Build(p<<1|1,mid+1,r);
	up(p);
}
void update(int p,int x,int d){
	if(l(p)==r(p)) return cnt(p,1)=d,void();
	int mid=(l(p)+r(p))>>1;
	if(x<=mid) update(p<<1,x,d);
	else update(p<<1|1,x,d);
	up(p);
}
void query(int p,int l,int r){
	if(l<=l(p) && r(p)<=r) return merge(p),void();
	int mid=(l(p)+r(p))>>1;
	if(l<=mid) query(p<<1,l,r);
	if(mid<r) query(p<<1|1,l,r);
}
ll solve(int l,int r){
	MS(Cnt,0);Len=0;
	query(1,l,r);
	if(Len<=2) return 0;
	for(int i=1;i<=Len-2;i++){
		if(Cnt[i+1]+Cnt[i+2]>=Cnt[i]) return 1ll*Cnt[i]+Cnt[i+1]+Cnt[i+2];
	}
	return 0;
}
int main(){
	IOS;cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	Build(1,1,n);
	for(int i=1,op,l,r;i<=q;i++){
		cin>>op>>l>>r;
		if(op==1) update(1,l,r);
		else cout<<solve(l,r)<<"\n";
	}
	return 0;
}


10.26 NOIP 模拟赛 十三

100+0+100+0=200 pts,rk5。

神秘比赛放两道黑。T4 73pts 很好想,懒得写了。

有大神 Vector_net T4 假做法拿满。

T1

AGC062A

神秘题,打表发现只有在 \(A\dots AB\dots B\) 时输出 \(B\)

T2

lg-P9339

黑题不会。

T3

给出了一个 \(N\) 个点 \(M\) 条边的有向图。求有多少个有序点对 \((a,b)\),满足至少存在一个点 \(c\) 以及从 \(c\)\(a\) 的一条路径 \(L_a\)\(c\)\(b\) 的一条路径 \(L_b\),使得 \(L_a\) 的长度是 \(L_b\) 的两倍(长度指的经过的边的数目)\((|L_a|,|L_b|\ge 0)\)
注意不一定是简单路径。

bfs,每次 \(a\) 走两步,\(b\) 走一步,分步进行。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=3e3+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,m;
bool vis[N][N][5];
vector<int> to[N];
struct node{
	int u,v,stp;
};
int main(){
	IOS;cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		to[u].pb(v);
	}
	int ans=0;
	queue<node> q;
	for(int i=1;i<=n;i++){
		vis[i][i][3]=1;
		q.push({i,i,3});
	}
	while(!q.empty()){
		node tp=q.front();q.pop();
		if(tp.stp==3){
			for(int v:to[tp.u]){
				if(vis[v][tp.v][1]) continue;
				q.push({v,tp.v,1});
				vis[v][tp.v][1]=1;
			}
		}
		else if(tp.stp==1){
			for(int v:to[tp.v]){
				if(vis[tp.u][v][2]) continue;
				q.push({tp.u,v,2});
				vis[tp.u][v][2]=1;
			}
		}
		else{
			for(int v:to[tp.v]){
				if(vis[tp.u][v][3]) continue;
				q.push({tp.u,v,3});
				vis[tp.u][v][3]=1;
			}
		}
	}	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) ans+=vis[i][j][3];
	}
	cout<<ans<<"\n";
	return 0;
}

T4

lg-P6881

黑不会,还没订正。

10.27 NOIP 模拟赛 十四

100+5+0+0=105pts,rk14。

T2 拼尽全力想出正解但写挂了。我太菜了

T1

你在一条数轴 \(1 \sim N\) 上放点,初始时 \(0\)\(N+1\) 已有点。
规则:选择间隔最大的两点(包括边界点,多个最长时选最左的),在两点中间的位置放入新点(如果两点中间的点数恰好是奇数,则放在正中间,否则停在正中间的左边位置)。
每个第 \(i\) 次放入的点有一个 \(a_i\),要求放入后,离它最近的 非边界点 的距离 严格大于 \(a_i\),否则这个点会消失。
你可以花费 \(1\) 的代价使 \(a_i\)\(1\)
求最小的代价,使让所有 \(M\) 个点都成功放下。
\(N\le 10^{12},M\le 10^6\)

简单题,考虑求出 \(pos_i\) 表示 \(i\) 放的位置。用优先队列维护每个区间即可。最后统计答案。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int m;
ll n,a[N],v[N];
struct lines{
	ll first,second;
};
priority_queue< lines,vector<lines>,greater<lines> > s;
bool operator > (const lines xx,const lines yy){
	if(xx.first==yy.first) return xx.second>yy.second;
	return xx.first<yy.first;
}
struct node{
	ll pos,id;
}b[N];
int main(){
	IOS;cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>a[i],b[i].id=i;
	b[0].pos=-INF,b[m+1].pos=INF;
	s.push((lines){n,1});
	for(int i=1;i<=m;i++){
		lines tp=s.top();s.pop();
		ll len=(tp.first+1)>>1;
		b[i].pos=tp.second+len-1;
		if((tp.first-1)>>1){
			s.push((lines){(tp.first-1)>>1,tp.second});
		}
		if(tp.first>>1){
			s.push((lines){tp.first>>1,b[i].pos+1});
		}
	}
	sort(b+1,b+1+m,[&](const node xx,const node yy){
		return xx.pos<yy.pos;
	});
	for(int i=1;i<=m;i++){
		v[b[i].id]=min(b[i].pos-b[i-1].pos,b[i+1].pos-b[i].pos);
	}
	ll ans=0;
	for(int i=1;i<=m;i++){
		if(a[i]-v[i]+1>0) ans+=a[i]-v[i]+1;
	}
	cout<<ans<<"\n";
	return 0;
}

T2

地图上有 \(n\) 个点,构成一棵树形结构。初始你在 \(1\) 号点,对于 \(2 ∼ n\) 中的每个点,上面都恰有一只怪兽。
每只怪兽都可以用一个二元组 \((a_i,b_i)\) 表示。
当你第一次碰到它时,它会与你开战,先对你造成 \(a_i\) 点伤害,战斗结束后你可以恢复 \(b_i\)点血量。此后再次经过它所在节点不会产生任何影响。
你可以在地图上移动并决策打怪兽的顺序,但如果某一时刻你的血量变成负数,那么你就输了。求取得最终胜利,初始血量的最小值。

神秘贪心题。

题目转化为初始血量为 \(0\),求在所有时刻血量最小值的最大值。

考虑将两个怪 \((a_1,b_1),(a_2,b_2)\) 合并,先打 \(1\) 在打 \(2\),会有:

\[(a',b')= \begin{cases} (a_1,b_1-a_2+b_2),b_1 \ge a_2\\ (a_1-b_1+a_2,b_2),b_1 < a_2 \\ \end{cases} \]

在考虑两只怪的优先级问题。

称一只怪 \(i\) 有益当且仅当 \(a_i \le b_i\)

如果在菊花图上,会有一个打怪策略:

  • 若一些怪有益,按 \(a_i\) 从小到大排。
  • 若一些怪无益,按 \(b_i\) 从大到小排。
  • 有益在无益前面。

这个顺序称为怪的优先级。

将其放到树上,如果一个点 \(u\) 的优先级最高的儿子 \(v\)。若它优先级比 \(v\) 高,因为 \(u\) 是所有能打的怪中优先级最高的的,那么打完 \(u\) 之后肯定会先打 \(v\)。那么将 \(u\)\(v\) 合并,直到最后只剩一个点为止。

可以写并查集维护或是 dfs + 启发式合并。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,ff[N];
ll a[N],b[N];
vector<int> to[N];
struct node{
	ll x,y;
	int id;
};
bool operator > (const node xx,const node yy){
	if(xx.x>=xx.y && yy.x<yy.y) return 1;
	else if(xx.x<xx.y && yy.x>=yy.y) return 0;
	else if(xx.x<=xx.y && yy.x<=yy.y) return xx.x>yy.x;		
	else return xx.y<yy.y;		
}
priority_queue<node,vector<node>,greater<node> > q[N];
void dfs(int u,int fa){
	ff[u]=fa;
	for(int v:to[u]){
		if(v==fa) continue;
		dfs(v,u);
		if(q[v].size()>q[u].size()) swap(q[v],q[u]);//启发式合并
	}
	for(int v:to[u]){
		if(v==fa) continue;
		while(!q[v].empty()) q[u].push(q[v].top()),q[v].pop();
	}
	while(!q[u].empty()){
		auto tp=q[u].top();
		if(node{a[u],b[u]}>tp){
			if(b[u]>=tp.x) b[u]-=tp.x-tp.y;
			else a[u]-=b[u]-tp.x,b[u]=tp.y;
			q[u].pop();
		}
		else break;
	}
	q[u].push({a[u],b[u],u});
}
int main(){
	IOS;cin>>n;
	for(int i=2;i<=n;i++) cin>>a[i]>>b[i];
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		to[u].pb(v);
		to[v].pb(u);
	}
	dfs(1,0);
	while(!q[1].empty()){
		node tp=q[1].top();q[1].pop();
		if(b[0]>=tp.x) b[0]-=tp.x-tp.y;
		else a[0]-=b[0]-tp.x,b[0]=tp.y;			
	}
	cout<<a[0]<<"\n";
	return 0;
}

T3

给定一棵 \(n\) 个节点的树,根为 \(1\),第 \(i\) 个点有一个权值 \(a_i\)
对每个点 \(u\),这次询问答案为其所在子树外的所有点中,选两个点 \(i,j\)\(ai,aj\) 异或后的最大值,如果选不出两个点,则认为 \(u\) 的答案是 \(0\)

原题 lg-P8511

01-Trie 维护,可以先求出整棵树的异或最大值以及位置,设为 \(x,y\),会有除了 \(1\)\(x\) 的路径以及 \(1\)\(y\) 路径上的点以外的点的答案都为 \(a_x \oplus a_y\)。再单独求 \(1\)\(x\) 的路径以及 \(1\)\(y\) 路径上的点的答案即可。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=5e5+5,M=N*61+5;
const ll INF=1ll<<60,mod=998244353;
int n,cnt=1,nxt[M][2],num[M],ff[N];
ll a[N];
struct Trie{
	void clear(){
		MS(nxt,0);MS(num,0);cnt=1;
	}
	void add(ll x,int id){
		int p=1;
		for(int i=59;~i;i--){
			int ch=(x>>i)&1;
			if(!nxt[p][ch]) nxt[p][ch]=++cnt;
			p=nxt[p][ch];
		}
		num[p]=id;
	}
	int query(ll x){
		int p=1;
		for(int i=59;~i;i--){
			int ch=(x>>i)&1;
			if(nxt[p][ch^1]) p=nxt[p][ch^1];
			else p=nxt[p][ch];
		}
		return num[p];
	}
}tr;
vector<int> to[N];
int ax,ay,dep[N],is[N];
ll maxx,ans[N],maxn[N];
void dfs1(int u){
	dep[u]=dep[ff[u]]+1;
	tr.add(a[u],u);
	int id=tr.query(a[u]);
	if((a[id]^a[u])>maxx){
		maxx=a[id]^a[u],ax=u,ay=id;
	}
	for(int v:to[u]) dfs1(v);
}
int Lca(int u,int v){
	if(dep[u]>dep[v]) swap(u,v);
	while(dep[u]<dep[v]) is[v]=2,v=ff[v];
	if(u==v) return u;
	while(u^v){
		is[u]=3,is[v]=2,u=ff[u],v=ff[v];
	}
	return u;
}
void dfs3(int u){
	tr.add(a[u],u);
	int id=tr.query(a[u]);
	maxn[u]=max(maxn[u],a[id]^a[u]);
	for(int v:to[u]){
		dfs3(v);
		maxn[u]=max(maxn[u],maxn[v]);
	}
}
void dfs2(int u,int fa,int ID){
	maxn[u]=ans[u]=maxn[fa];
	tr.add(a[u],u);
	maxn[u]=max(maxn[u],a[tr.query(a[u])]^a[u]);
	for(int v:to[u]){
		if(is[v]==1 || is[v]==ID) continue;
		dfs3(v);
		maxn[u]=max(maxn[u],maxn[v]);
	}
	for(int v:to[u]){
		if(is[v]==ID || is[v]==1) dfs2(v,u,ID);
	}
}
int main(){
	IOS;cin>>n;
	for(int i=2;i<=n;i++){
		cin>>ff[i];
		to[ff[i]].pb(i);
	}
	for(int i=1;i<=n;i++) cin>>a[i];
	dfs1(1);
	int lc=Lca(ax,ay),nc=lc;
	while(lc) is[lc]=1,lc=ff[lc];
	int ttt=0;
	for(int v:to[nc]) ttt+=is[v]!=0;
	for(int i=1;i<=n;i++) if(!is[i]) ans[i]=maxx;
	tr.clear();dfs2(1,0,2);
	MS(maxn,0);
	if(ttt==2) tr.clear(),dfs2(1,0,3);
	for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
	return 0;
}

T4

比较抽象。

10.28 NOIP 模拟赛 十五

100+100+0+0=200pts,rk3

被 T2 暴杀,导致 T3 想出做法来不及写。

T1

给你一棵 \(n\) 个节点的树, 每次询问包含第 \(i\) 条边的树上最长路径长度。

糖题,但我写了 1 个小时。我是废物

求一下直径即可。也可以换根 dp,但要特判在根的情况。

Code

屎山代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e6+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,d1,d2,dis[N],ans[N],pos[N],tot,bl[N],g[N],Dis[N],Id[N];
PII ff[N],e[N];
vector<PII> to[N];
bool vis[N],is[N];
void dfs1(int u,int fa){
	for(PII tp:to[u]){
		int v=tp.first,id=tp.second;
		if(v==fa) continue;
		ff[v]={u,id};
		dis[v]=dis[u]+1;
		dfs1(v,u);
	}
}
void dfs2(int u,int fa,int tf){
	for(PII tp:to[u]){
		int v=tp.first,id=tp.second;
		if(v==fa) continue;
		if(is[v]) dfs2(v,u,tf),bl[id]=tf;
		else if(is[u]){
			dis[v]=dis[u]+1;
			dfs2(v,u,u);
			Dis[u]=max(Dis[u],Dis[v]+1);bl[id]=u;
		}
		else{
			dis[v]=dis[u]+1;
			dfs2(v,u,tf);
			Dis[u]=max(Dis[u],Dis[v]+1);bl[id]=tf;
		}
	}
}
void get_d(){
	PII tp={0,1};
	dfs1(1,0);
	for(int i=1;i<=n;i++) tp=max(tp,{dis[i],i});
	d1=tp.second;
	MS(ff,0);MS(dis,0);dfs1(d1,0);
	tp={0,d1};
	for(int i=1;i<=n;i++) tp=max(tp,{dis[i],i});
	d2=tp.second;
}
int main(){
	IOS;cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		to[u].eb(v,i);
		to[v].eb(u,i);
		e[i]={u,v};
	}
	get_d();
	int now=d2;
	while(now){
		PII tp=ff[now];
		is[now]=1;
		pos[++tot]=now;
		bl[tp.second]=d1;
		now=tp.first;
	}
	for(int i=1;i<=tot;i++) g[pos[i]]=max(i-1,tot-i);
	MS(dis,0);
	dfs2(d1,0,d1);
	for(int i=1;i<n;i++){
		if(bl[i]!=d1){
			int u=e[i].first,v=e[i].second;
			if(Dis[u]>Dis[v]) swap(u,v);
			ans[i]=g[bl[i]]+Dis[u]+dis[u];
		}
		else ans[i]=tot-1;
	}
	for(int i=1;i<n;i++) cout<<ans[i]<<"\n";
	return 0;
}

T2

给出一个 \(n\) 个点,\(m\) 条边的无向图。接下来 \(Q\) 次询问,每次从图中删掉一个点 \(A\) 后,请问此时点 \(B\) 到点 \(C\) 的最短路长度。
每次询问独立。

Floyd + 分治 + 离线。

\(solve(l,r)\) 表示不经过 \(l \sim r\) 这些点的最短路。当 \(l=r\) 时即为删去 \(l\) 点时的答案。

具体的在递归进 \(solve(mid+1,r)\) 时要先将 \(l \sim mid\) 的点处理了。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=200+5,M=1e5+5;
const ll INF=1e9,mod=998244353;
int n,m,Q;
ll f[N][N][N],ans[M];
struct Qry{
	int u,v,id;
};
vector<Qry> q[N];
void solve(int l,int r,int d){
	if(l==r){
		for(Qry tp:q[l]) ans[tp.id]=f[d-1][tp.u][tp.v];
		return ;
	}
	int mid=(l+r)>>1;
	MC(f[d],f[d-1]);
	for(int k=mid+1;k<=r;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[d][i][j]=min(f[d][i][j],f[d][i][k]+f[d][k][j]);
			}
		}
	}
	solve(l,mid,d+1);
	MC(f[d],f[d-1]);
	for(int k=l;k<=mid;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				f[d][i][j]=min(f[d][i][j],f[d][i][k]+f[d][k][j]);
			}
		}		
	}
	solve(mid+1,r,d+1);
}
int main(){
	IOS;cin>>n>>m>>Q;
	for(int i=0;i<=n+1;i++){
		for(int j=0;j<=n+1;j++){
			for(int k=0;k<=n+1;k++) f[i][j][k]=INF;
		}
	}
	for(int i=1,u,v;i<=m;i++){
		ll w;cin>>u>>v>>w;
		f[0][u][v]=f[0][v][u]=min(f[0][u][v],w);
	}
	for(int t=1,x,y,z;t<=Q;t++){
		cin>>x>>y>>z;
		q[x].pb({y,z,t});
	}
	solve(1,n,1);
	for(int i=1;i<=Q;i++) cout<<(ans[i]==INF?-1:ans[i])<<"\n";
	return 0;
}


T3

数轴上有 \(n\) 个点,第 \(i\) 个点的位置为 \(p_i\),权值为 \(a_i\)
有以下两种操作:

  1. \(A~~l~~r~~c\)\(\forall i \in [l, r],\ a_i \gets a_i + c\)
  2. \(B~~x~~y\):将数组 \(a_{\min(x,y)\sim \max(x,y)}\)\(x\) 循环移动 \(1\) 位。
    即假设 \(a_{\min(x,y)\sim \max(x,y)}=[1,2,3,4,5]\),若 \(x=5,y=1\),数组将变为 \([5,1,2,3,4]\);若 \(x=1,y=5\),数组变为 \([2,3,4,5,1]\)

最开始和每次操作后求 \(P\) 满足 \(\sum\limits_{i=1}^n a_i\times|p_i - P|\) 最小,并输出 \(P\)

FHQ_Treap 板题,但考试时没时间写了。

维护区间加,区间翻转。

\(x,y\) 的循环位移可以拆成两个区间翻转。

设权值和为 \(s\),答案为平衡树上二分找到第一个点 \(x\) 满足 $ \sum \limits_{i=1}^{x} val_i> \lfloor \frac{s+1}{2} \rfloor$ 的 \(p_x\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll __int128
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define int ll
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=1e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
namespace slow{
	char Buf[1<<23],*P1=Buf,*P2=Buf;
	#define getchar() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
	#define T template<typename type>
	#define Ts template<typename type,typename... args>
	T inline void read(type &x){
		x=0;bool f=false;char ch=getchar();
		while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
		while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
		if(f) x=-x;
	}
	T inline void write(type o){
	    if(o<0) putchar('-'),o=-o;
	    if(o>9) write(o/10);putchar(o%10+'0');
	}
	Ts inline void read(type &x,args&... y){read(x),read(y...);}
	Ts inline void write(type x,args... y){write(x),putchar(' '),write(y...);putchar('\n');}	
}using namespace slow;
struct Tree{
	int l,r;
	ll siz,val,swp,pri,add,pre;
	void tag(int d){
		val+=d;add+=d;pre+=siz*d;
	}
	#define l(p) t[p].l
	#define r(p) t[p].r
	#define siz(p) t[p].siz
	#define val(p) t[p].val
	#define swp(p) t[p].swp
	#define pri(p) t[p].pri
	#define add(p) t[p].add
	#define pre(p) t[p].pre
}t[N];
int n,m,cnt;
int root,roota,rootb,rootc;
struct FHQ_Treap{
	int newnode(int x){
		t[++cnt]={0,0,1,x,0,rand(),0,x};
		return cnt;
	}
	void up(int p){
		if(!p) return ;
		siz(p)=siz(l(p))+siz(r(p))+1;
		pre(p)=val(p)+pre(l(p))+pre(r(p));
	}
	void down(int p){
		if(swp(p)){
			swap(l(p),r(p));
			if(l(p)) swp(l(p))^=1;
			if(r(p)) swp(r(p))^=1;
			swp(p)=0;
		}
		if(add(p)){
			if(l(p)) t[l(p)].tag(add(p));
			if(r(p)) t[r(p)].tag(add(p));
			add(p)=0;
		}
	}
	void split(int p,int x,int &L,int &R){
		if(!p) return L=R=0,void();
		down(p);
		if(siz(l(p))<x) L=p,split(r(p),x-siz(l(p))-1,r(p),R);
		else R=p,split(l(p),x,L,l(p));
		up(p);
	}
	int merge(int x,int y){
		if(!x || !y) return x|y;
		down(x),down(y);
		if(pri(x)<pri(y)) return r(x)=merge(r(x),y),up(x),x;
		else return l(y)=merge(x,l(y)),up(y),y;
	}
	void update(int l,int r,ll d){
		split(root,r,roota,rootb);
		split(roota,l-1,roota,rootc);
		t[rootc].tag(d);
		roota=merge(roota,rootc);
		root=merge(roota,rootb);
	}
	void Reverse(int l,int r){
		split(root,r,roota,rootb);
		split(roota,l-1,roota,rootc);
		swp(rootc)^=1;
		roota=merge(roota,rootc);
		root=merge(roota,rootb);		
	}
	void out(int p){
		if(!p) return ;
		down(p);
		out(l(p));
		write(val(p)),puts(" ");
		out(r(p));
	}
	int query(ll x){
		int p=root,ans=0;
		while(p){
			down(p);
			if(!l(p)){
				ans++;
				if(val(p)>=x) return ans;
				x-=val(p),p=r(p);
			}
			else if(pre(l(p))>=x) p=l(p);
			else if(pre(l(p))+val(p)>=x) return ans+siz(l(p))+1;
			else ans+=siz(l(p))+1,x-=pre(l(p))+val(p),p=r(p);
		}
		return ans;
	}
}tr;
ll pos[N];
void out(int p){
	cout<<"out************\n";
	tr.out(p);
	cout<<"\n************\n";
}
signed main(){
	read(n,m);
	for(int i=1,x;i<=n;i++){
		read(x);
		root=tr.merge(root,tr.newnode(x));
	}
	for(int i=1;i<=n;i++) read(pos[i]);
	write(pos[tr.query((pre(root)+1)/2)]);puts("");
	for(int i=1,l,r,c;i<=m;i++){
		char op=getchar();
		read(l,r);
		if(op=='A'){
			read(c);
			tr.update(l,r,c);
		}
		else{
			if(l<r) tr.Reverse(l,r),tr.Reverse(l,r-1);
			else tr.Reverse(r,l),tr.Reverse(r+1,l);
		}
		write(pos[tr.query((pre(root)+1)/2)]);puts("");
	}
	return 0;
}


T4

神秘题,不会。

10.30 NOIP 模拟赛 十六

100+100+0+0=200 pts,rk2

CSP-S 前最后一场模拟赛,膜拜 bbbzzx rk1。
T1,T2全做过原题,感觉 CSP-S 要爆炸了怎么办。

T1

lg-P9188

简单 dp,但容易写假。

\(f_i\) 表示以 \(i\) 结尾子串总和,找到最右边的 \(j\) 满足 \(s_{j\sim i}\) 是包含 bessie 的。

\(f_i=f_{j-1}+j\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=3e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n;
ll f[N],pre[N][30];
char a[N];
int get(int id){
    return pre[pre[pre[pre[pre[pre[id][5]][9]][19]][19]][5]][2];
}
int main(){
    IOS;cin>>(a+1);n=strlen(a+1);
	for(int i=1;i<=n;i++){
        MC(pre[i+1],pre[i]);
        pre[i+1][a[i]-'a'+1]=i;
    }
    for(int i=1;i<=n;i++){
        ll j=get(i+1);
        if(j) f[i]=f[j-1]+j;
    }
    ll ans=0;
    for(int i=1;i<=n;i++) ans+=f[i];
    cout<<ans<<"\n";
    return 0;
}

T2

lg-P8907

启发式合并,将 \(i\) 连的边转移到编号最小的点,延迟连边。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
using namespace std;
const int N=2e5+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,m;
set<int> s[N];
int main(){
	IOS;cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		if(u>v) swap(u,v);
		s[u].insert(v);
	}
	ll ans=-m;
	for(int i=1;i<=n;i++){;
		if(s[i].empty()) continue;
		ans+=s[i].size();
		int u=*s[i].begin();s[i].erase(s[i].begin());
		if(s[i].size()>s[u].size()) swap(s[i],s[u]);
		for(int x:s[i]) s[u].insert(x);
	}
	cout<<ans<<"\n";
	return 0;
}

T3

lg-P8906

想到 meet in the middle,但以为复杂度不正确,就没往后面想了。

正解:

反过来改成加边。用 meet in the middle 后只用考虑其中一部分。

维护恰好经过两条边的最短路 \(d_{i,j}\)

分类讨论,若加边 \((u,v,w)\),起点为 \(s\)

  • \(k=1\):当且仅当 \(u=s\) 会贡献到答案里。
  • \(k=2\):可以计算:\(d_{i,v}=\min(d_{i,v},d_{i,u}+w)\)\(d_{u,i}\) 同理。在枚举答案 \(ans_i=\min(ans_i,d_{s,i})\)
  • \(k=3\):会发现 \((u,v,w)\)\(u=s\) 的只有 \(n\) 个,可以 \(n^2\) 转移,其余可以 \(\mathcal{O(n)}\) 转移。
  • \(k=4\):同 \(k=3\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) (x&-x)
using namespace std;
const int N=300+5,M=N*N+5,P=11;
const ll INF=1e9+7,mod=998244353;
int n,m,k,dis[N][N];
ll ans[M];
bool vis[N][N];
struct edge{
	int u,v;
}e[M];
struct node{
	ll s,k,to[N][N],d[N][N],ans[M];
	void init(int x,int K){
		s=x;k=K;
		MS(to,0x3f);MS(d,0x3f);MS(ans,0x3f);
	}
	void insert(int u,int v,int w){
		to[u][v]=w;
		if(k==1){
			if(s==u) ans[v]=w;
			return ;
		}
		for(int i=1;i<=n;i++){
			d[i][v]=min(d[i][v],to[i][u]+w);
			d[u][i]=min(d[u][i],to[v][i]+w);
		}
		if(k==2){
			for(int i=1;i<=n;i++) ans[i]=min(ans[i],d[s][i]);
			return ;
		}
		if(k==3){
			if(s==u){
				for(int i=1;i<=n;i++){
					for(int j=1;j<=n;j++){
						ans[j]=min(ans[j],d[s][i]+to[i][j]);
					}
				}
			}
			else{
				for(int i=1;i<=n;i++){
					ans[i]=min({ans[i],to[s][u]+d[u][i],d[s][v]+to[v][i]});
					ans[v]=min(ans[v],d[s][i]+to[i][v]);
					ans[u]=min(ans[u],d[s][i]+to[i][u]);
				}
			}
			return ;
		}
		if(k==4){
			if(s==u){
				for(int i=1;i<=n;i++){
					for(int j=1;j<=n;j++){
						ans[j]=min(ans[j],d[s][i]+d[i][j]);
					}
				}
			}
			else{
				for(int i=1;i<=n;i++){
					ans[i]=min({ans[i],d[s][u]+d[u][i],d[s][v]+d[v][i]});
					ans[v]=min(ans[v],d[s][i]+d[i][v]);
					ans[u]=min(ans[u],d[s][i]+d[i][u]);
				}
			}
			return ;
		}		
	}
}s,t;
int main(){
	IOS;cin>>n>>k;m=n*n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) cin>>dis[i][j];
	}
	for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v;
	s.init(1,k/2);t.init(n,k-k/2);
	for(int tt=m;tt>=1;tt--){
		ans[tt]=INF;
		for(int i=1;i<=n;i++) ans[tt]=min(ans[tt],s.ans[i]+t.ans[i]);
		int u=e[tt].u,v=e[tt].v,w=dis[u][v];
		s.insert(u,v,w);
		t.insert(v,u,w);
	}
	for(int i=1;i<=m;i++) cout<<(ans[i]>=INF?-1:ans[i])<<"\n";
	return 0;
}

T4

lg-P9112

黑题不会

11.16 NOIP 模拟赛 十七

CSP-S 果然炸了。。。

100+100+50+5=255pts rk2
被 TheShuMo 以压倒性的 349 暴杀。膜拜 Vector_net 做出 T3,暴切紫题%%%%%%%。

T1

九条可怜和 Alice 有一个序列,记作 \(a\),其中有 \(n\) 个元素,编号从 \(a_1\)\(a_n\)。游戏会进行若干轮直到序列中没有元素。
在每一轮中,九条可怜会先选择一个数,如果这个数是偶数,那么九条可怜的得分就会加上这个数。之后将这个数删掉。接着 Alice 会进行同样的操作,不同的是,如果是奇数,那么 Alice 的得分会加上这个数。
问两人采取最优策略的情况下,谁最后会胜利。

唐题,每次取最大的数。

T2

你需要计算满足下列长度为 \(m\) 的序列 \(a\) 的个数:

  • \(m\ne 0\)
  • \(a_i<a_i+1,1≤i<m\)
  • \(a_m≤n\)
  • \(a_i \oplus a_{i+1} \oplus a_{i+2}\ne 0,1\le i\le m−2\)

\(\oplus\) 表示异或。对给定数 \(p\) 取模。\(n \le 10^6\)

dp 题,挺难的。但我的做法很唐,仅供参考。
题目求的所有长度的方案数总和。
考虑若 \(n \le 2000\):
可以设 \(f_{i,j}\) 表示最大的数为 \(i\),最后两个异或和为 \(j\) 的方案数,即上一个数为 \(k=i \oplus j\)。有:

\[f_{i,j}=\sum\limits_{x,x\oplus i \ne 0} f_{k,x}=\sum\limits_{x} f_{k,x}~-f_{k,j} \]

记一下和。复杂度 \(\mathcal{O(n^2)}\)

可以把状态改为 \(g_i=\sum f_{i,j}\)。有:

\[g_i=\sum_j f_{i,j}=\sum_j ~(~\sum\limits_{x} f_{k,x}~) - f_{k,j}=\sum_k^{i-1} g_k-f_{i\oplus k,k}\\ =\sum_k^{i-1} g_k-\sum\limits_{j=1}^{i-1} [i\oplus j<i] g_j \]

会发现若 \(i\oplus j<i\),则有 \(j\) 的最高位在 \(i\) 中为 \(1\) 且不为 \(i\) 的最高位。

因此记一下前缀和再删去一部分即可。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60;
namespace H_H{
	int n,mod,num[N],K[N];
	ll f[N],s[N];
	int main(){
		cin>>n>>mod;
		if(n==1) cout<<1%mod<<"\n",exit(0);
		ll sum=3;
		f[1]=1;s[1]=1;
		f[2]=2;s[2]=2;
		for(int i=1;i<=n;i++) num[i]=num[i>>1]+1;
		for(int t=3;t<=n;t++){
			f[t]=(sum+1)%mod;
			for(int i=t;lb(i)!=i;i-=lb(i)) f[t]=(f[t]-s[num[lb(i)]]+mod)%mod;
			sum=(sum+f[t])%mod;
			s[num[t]]=(s[num[t]]+f[t])%mod;
		}
		cout<<sum%mod<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T3

九条可怜有一个棋盘,是 \(n×(m+1)\) 的,上面有 \(n\) 个棋子,其中每行恰好有一个棋子,记第 \(i\) 行的棋子的纵坐标为 $ai,列的编号从 \(0\)\(m\)

如果一个棋子左边没有棋子,且左边不是棋盘边界,那么就称这个棋子是可以连续穿梭的。

Bob会和Alice玩游戏,九条可怜会每次先选择两个数 \(l,r\) 表示只保留棋盘的 \([l,r]\) 列内的格子和棋子。

Alice先手,每个人每次选择一枚棋子,如果这枚棋子是可以连续穿梭的,那么她会使这个棋子向左移动任意格。第一个不能操作的人输。

九条可怜在旁边电灯泡吃瓜津津有味,因此九条可怜给出了 \(Q\) 局的 \(l,r\),想让你求出谁会胜利。
对于 \(100\%\) 的数据,\(n,q,m≤3×10^5\)

会发现这是一个 Nim 博弈论。

相当于若 \(\bigoplus\limits_{i=l}^r(a_i-l)=0\) 则先手必输,否则必赢。

暴力可以拿 \(50\) 分。

可以拿 ST 表优化,按位来考虑,拿桶记一下每一种数的个数。设 \(f_{i,j}\) 表示只考虑 \([i,i+2^j-1]\) 内的数在均减去 \(i\) 的情况下的异或和。

需要考虑如何将 \(f_{i,j-1}\)\(f_{i+2^{j-1},j-1}\) 合并。由于对于两个区间的数在减完后一定小于 \(2^{j-1}\),所以只用考虑第 \(j-1\) 的情况。

会发现在右部分会多减 \(2^{j-1}\),因此要把它加回来。由于异或的特性,只有在多减的数为奇数个时才会为 \(1\)。判断一下即可。

查询也是按位考虑。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=3e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n,m,q,num[N],f[N][23];
	int main(){
		IOS;cin>>n>>m;
		for(int i=1,x;i<=n;i++) cin>>x,num[x]++;
		for(int i=1;i<=m;i++) num[i]+=num[i-1];
		for(int j=1;j<=__lg(m);j++){
			for(int i=0;i+(1<<j)-1<=m;i++){
				f[i][j]=f[i][j-1]^f[i+(1<<(j-1))][j-1];
				if((num[i+(1<<j)-1]-num[i+(1<<(j-1))-1])&1) f[i][j]^=1<<(j-1);
			}
		}
		cin>>q;
		for(int i=1,l,r;i<=q;i++){
			cin>>l>>r;
			int tp=0,now=l;
			for(int t=20;~t;t--){
				if(now+(1<<t)<=r){
					tp^=f[now][t];now+=1<<t;
					if((num[r]-num[now-1])&1) tp^=1<<t;
				}
			}
			if(!tp) cout<<"B";
			else cout<<"A";
		}
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T4

题目看不懂,不会。

11.17 NOIP 模拟赛 十八

100+100+68+36=304 pts,rk3 败在两位高二大神上。

T1

唐题

T2

给定 \(N\)\(M\) 位的 \(01\) 串。对于每个串,求与它不一样位的个数最多的串,输出位数。\(N\le 10^5,M\le 18\)

发现与 NOIP 模拟赛十一的 T2 几乎一样,将代价改为 \(1\) 即可。

复杂度 \(\mathcal{O(2^m m^2)}\)。赛后发现可以 \(\mathcal{O(2^m m)}\),懒得管了。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e5+20,M=(1<<18)+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int m,n,a[N],num[M],f[20][M][20];
	int main(){
		cin>>m>>n;
		for(int i=1;i<=n;i++){
			for(int j=0;j<m;j++){
				char ch;cin>>ch;
				if(ch=='G') a[i]|=1<<j;
			}
			num[a[i]]=1;
		}
		for(int i=0;i<(1<<m);i++) f[0][i][0]=num[i];
		for(int i=1;i<=m;i++){
			for(int t=0;t<(1<<m);t++){
				for(int j=0;j<=m;j++){
					if(j) f[i][t][j]=f[i-1][t][j-1];
					f[i][t][j]|=f[i-1][t^(1<<(i-1))][j];
				}
			}
		}
		for(int i=1;i<=n;i++){
			int c=(~a[i])&((1<<m)-1);
			for(int j=m;~j;j--){
				if(f[m][c][j]){
					cout<<j<<"\n";
					break;
				}
			}
		}
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T3

lg-P8908

死在了复杂度分析上。我还是太菜了。

先考虑暴力的做法。

\(\texttt{G}\)\(1\)\(\texttt{H}\)\(0\)

假设枚举到的区间为 \([L,R]\),有 \(k\)\(1\),位置为 \(a_{1\sim k}\),满足其为回文串的充要条件是 \(1\) 是对称的。

\([L,R]\) 长度为偶数且 \(1\) 的个数为奇数时显然是无解的。

要满足交换的次数最少,则一定是 \(a_1\)\(a_k\) 交换后关于中心对称,\(a_2\)\(a_{k-1}\) 后关于中心对称等。贡献为 \(| a_i-L-(R-a_{k-i+1})|=| a_i+a_{k-i+1}-L-R|\),即 \(i\)\(k-i+1\) 两个分别在中间的两侧。

每次求可以固定 \(L\),让 \(R\) 往右枚举,把为 \(1\) 的位置记录下来。复杂度 \(\mathcal{O(n^3)}\)

正解:

\(k\) 为奇数,可以固定第 \(\lfloor\frac{k+1}{2}\rfloor\) 的数,偶数同理。每次向两边各扩展一个 \(1\),设分别为 \(a_l,a_r\)。那么 \(L \in (a_{l-1},a_l]\)\(R \in [a_r,a_{r+1})\)

可以直接枚举 \(L,R\),对于每个 \(L,R\) 只会被枚举一次。因为 \(a_l,a_r\) 中点只有一个(显然的),它只会被枚举到一次,所以 \(L \in (a_{l-1},a_l]\)\(R \in [a_r,a_{r+1})\) 也只会被枚举到一次。

拿树状数组维护有多少个数比 \(L+R\) 大以及它们的和,有多少个数比 \(L+R\) 小以及它们的和。分别计算贡献即可。复杂度 \(\mathcal{O(n^2\log n)}\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=7500+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n,a[N],b[N],cnt;
	struct BIT{
		ll s[N<<1],p[N<<1];
		inline void clear(){MS(s,0);MS(p,0);}
		inline void add(int x){
			int tp=x;
			for(;x<=2*n;x+=lb(x)) s[x]+=tp,p[x]+=1;
		}
		inline PLL query(int x){
			ll ans=0,Ans=0;
			for(;x;x-=lb(x)) ans+=s[x],Ans+=p[x];
			return {ans,Ans};
		}
	}bit;
	PLL operator - (const PLL xx,const PLL yy){
		return {xx.first-yy.first,xx.second-yy.second};
	}
	inline void input(){
		char ch;
		while(cin>>ch) a[++n]=ch=='G';
	}
	int main(){
		input();
		ll ans=0;
		for(int l=n;l;l--){
			cnt=0;
			for(int r=l;r<=n;r++){
				if(a[r]) b[++cnt]=r;
				if((r-l)&1 && cnt&1){//无解
					ans--;
					continue;
				}
				if(cnt&1) ans+=abs(((l+r)>>1)-b[(cnt>>1)+1]);
			}
		}
		b[cnt+1]=n+1;
		for(int i=1;i<cnt;i++){//k 为偶数
			bit.clear();
			for(int l=i,r=i+1;l>=1 && r<=cnt;l--,r++){
				bit.add(b[l]+b[r]);
				for(int L=b[l-1]+1;L<=b[l];L++){
					for(int R=b[r];R<=b[r+1]-1;R++){
						int num=L+R;
						PLL num_1=bit.query(num);
						PLL num_2=bit.query(2*n)-num_1;
						ans+=(num_1.second-num_2.second)*num+num_2.first-num_1.first;
					}
				}
			}
		}
		for(int i=2;i<cnt;i++){//k 为奇数
			bit.clear();
			for(int l=i-1,r=i+1;l>=1 && r<=cnt;l--,r++){
				bit.add(b[l]+b[r]);
				for(int L=b[l-1]+1;L<=b[l];L++){
					for(int R=b[r];R<=b[r+1]-1;R++){
						if((L-R)&1) continue;
						int num=L+R;
						PLL num_1=bit.query(num);
						PLL num_2=bit.query(2*n)-num_1;
						ans+=(num_1.second-num_2.second)*num+num_2.first-num_1.first;
					}
				}
			}
		}
		cout<<ans<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T4

lg-P9192

动态 DP,不会。

11.18 NOIP 模拟赛 十九

100+10+0+0=110 pts,rk1145141919810

T1

有一天有 \(m\) 只虱子旅行到你的头上。虱子会在 \(10^6\) 天后离去,但是这 \(10^6\) 天显然非常难熬。如果某一天第 i 只虱子在你头上活动,你的难受程度会加上 \(c_i\)

为了定量计算你的难受程度,你和虱子首领要到了 \(n\) 条信息,每个信息可以表示为 \(L,R,K\),以及 \(K\) 个整数 \(A_i\),表示在 \(L_i\)\(R_i\) 天范围内 \(A\) 所表示的 \(K\) 只虱子会在你头上活动。注意,如果多条信息都提到了某个虱子在某一天活动过,你这一天的难受程度还是只会计算一次。

但是不幸的是,信息在到你手上之前先经过了跳蚤手上,跳蚤为了让你不能定量计算,于是加入了一条伪造的信息。

你无法分辨哪一条是伪造的信息,因此你决定对于每条信息,如果除了这条信息以外的信息都是真的话,你的难受程度总和是多少。

对于 \(100\%\) 的数据,\(1\le n,m\le 5×10^5,1\le L_i\le R_i \le 10^6,1\le \sum K\le 10^6,1≤K≤m,1≤c_i≤10^6\)

说实话并非简单。

将每只跳蚤的贡献分开考虑。对于每只跳蚤它对第 \(i\) 条信息的贡献为将它在从第 \(i\) 条信息去除后减少的难受程度。直接做前缀和以及统计前缀区间为 \(1\) 的个数。复杂度 \(\mathcal{O(K\times 10^6)}\)

用类似于扫描线的方法。将所有 \(L,R\) 离散,将一个点当作一个区间求。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	struct node{
		int id,l,r;
		node(){id=l=r=0;}
		node(int _id,int _l,int _r){
			id=_id,l=_l,r=_r;
		}
		bool operator < (const node xx){
			if(l==xx.l) return r<xx.r;
			return l<xx.l;
		}
	};
	vector<node> a[N];
	int n,m,c[N],b[N],s[N];
	ll f[N],ans[N];
	int main(){
		cin>>n>>m;
		for(int i=1;i<=m;i++) cin>>c[i];
		for(int i=1,L,R,K;i<=n;i++){
			cin>>L>>R>>K;
			for(int j=1,x;j<=K;j++){
				cin>>x;
				a[x].pb(node(i,L,R));
			}
		}
		ll sum=0;
		for(int i=1;i<=m;i++){
			if(!a[i].size()) continue;
			int cnt=0;
			for(node tp:a[i]) b[++cnt]=tp.l,b[++cnt]=tp.r+1;
			b[++cnt]=1;
			sort(b+1,b+1+cnt);//离散
			cnt=unique(b+1,b+1+cnt)-b-1;
			for(int j=1;j<=cnt;j++) s[j]=0;
			for(node tp:a[i]){//差分
				int ld=lower_bound(b+1,b+1+cnt,tp.l)-b;
				int rd=lower_bound(b+1,b+1+cnt,tp.r+1)-b;
				s[ld]++,s[rd]--;
			}
			for(int j=1;j<=cnt;j++) s[j]+=s[j-1];
			for(int j=2;j<=cnt;j++){
				f[j]=f[j-1]+(s[j-1]==1?(b[j]-b[j-1]):0);
				sum+=1ll*c[i]*(s[j-1]?(b[j]-b[j-1]):0);
			}
			for(node tp:a[i]){//统计
				int ld=lower_bound(b+1,b+1+cnt,tp.l)-b;
				int rd=lower_bound(b+1,b+1+cnt,tp.r+1)-b-1;
				ans[tp.id]-=1ll*(f[rd+1]-f[ld])*c[i];
			}
		}
		for(int i=1;i<=n;i++) cout<<sum+ans[i]<<" ";cout<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T2

有 n×n 个点排成了一个 n 行 n 列的点阵,对于每个 1≤i,j≤n,(i,j) 上都有一个点。

但是全部 n×n 个点去同一个地方总不太好,因此一部分点想要去 T,一部分点想要去 P。怎么办呢?

珂爱的点们决定,用一条直线将平面分成两半,直线左边的去T,有边的去P。

特别的,如果直线左边或者右边没有点,那么这种划分方案是不合法的。如果这条直线经过一个点,那么这个点就不知道自己去哪儿,因此也是不合法的。

求本质不同的划分方案数。两个方案本质不同定义为:一个点在其中一个方案中去了 P 而另一个去了 T。\(n\le 10^4\)

sb 题,做不来.

等价于矩阵可见点对个数。

可以考虑一条至少经过两个点的直线,可以将其按两点中间顺时针旋转一点角度即可得到一种方案。

会发现可以按顺时针和逆时针转,但会有算重的情况,故只统一往一个方向转。

要那数组记录一下 \(\gcd\),复杂度 \(\mathcal{O(n^2)}\),可以拿莫反优化到 \(\mathcal{O(n)}\),用杜教筛优化到到 \(\mathcal{O(n^{\frac{2}{3}})}\)

Code

给出 \(\mathcal{O(n^2)}\) 做法。注意可能算重的地方。

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
//#define I inline
#define Tp template<typename Tt>
#define Ts template<typename Tt,typename ...args>
Tp inline Tt lb(Tt x){return x&-x;}
using namespace std;
const int N=1e4+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int gd[N][N];
	inline int gcd(int a,int b){
		if(gd[a][b]) return gd[a][b];
		if(!b) return a;
		return gd[a][b]=gcd(b,a%b);
	}
	int n,p;
	int main(){
		cin>>n>>p;
		ll ans=0;
		for(int i=0;i<=n;i++){
			for(int j=0;j<i;j++){
				if(gcd(i,j)==1) ans+=1ll*(n-i)*(n-j);
			}
		}
		ans*=4;
		ans-=(n-1)*2;
		cout<<ans%p<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T3

不会。

T4

不会。

11.20 NOIP 模拟赛 二十

100+100+80+0=280pts,rk4。
凭什么 T3 都是 \(\mathcal{O(\frac{n^3\log n}{w})}\) 他们的跑这么快,甚至暴力能过。

T1

M 得到了一个正整数序列 \(a\),值域为 \([1,m]\)。方便起见 \(a\) 中的数互不相同。

M 非常喜欢完全平方数,因此 M 喜欢找到一对 \(i,j\),计算 \(a^2_i+a_j\),同时要求这也是一个完全平方数。

但是 M 并不会这样复杂的问题,请你告诉它这样的 \((i,j)\) 的对数。

\(a_i^2+a_j=x^2\),移项得 \(a_j=(x-a_i)(x+a_i)\),考虑枚举 \(a_j\) 由哪两个数相乘得到。直接做即可。

预处理因数,复杂度 \(\mathcal{O(m\log m)}\)

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
#define int ll
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e6+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H_READ{
	char Buf[1<<23],*P1=Buf,*P2=Buf;
	#define gc() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
	Tp inline void read(T &x){
		x=0;int ch=gc();bool f=0;
		while(!isdigit(ch)) f=ch=='-',ch=gc();
		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=gc();
		if(f) x=-x;
	}
	Tp inline void write(T x){
		if(x<0) putchar('-'),x=-x;
		if(x>9) putchar(x/10);putchar(x%10+'0');
	}
	Ts inline void read(T &x,args&... y){read(x),read(y...);}
	Ts inline void write(T x,args... y){write(x),putchar(' '),write(y...);puts("");}	
}using H_H_READ::read;using H_H_READ::write;
namespace H_H{
	int n,m;bool a[N];
	int main(){
		read(n,m);
		for(int i=1,x;i<=n;i++) read(x),a[x]=1;
		ll ans=0;
		for(int t=1;t<=m;t++){
			for(int i=1;i<=m/t;i++){
				int u=t,v=i;
				if(!a[i*u] || (u+v)&1) continue;
				if(u>v) swap(u,v);
				ans+=a[(v-u)>>1];
			}
		}
		cout<<ans/2<<"\n";
		return 0;
	}
}
signed main(){
	H_H::main();
	return 0;
}

T2

给出一个 \(1\)\(N\) 的排列,你需要求出有多少个区间 \([L,R]\),满足这个区间的值是连续的。比如 \(2,3,1\) 是一个合法的区间,而 \(3,1\) 不是。

对于一个区间 \([l,r]\) 若符合条件就等价于 \(mx-mn+l-r=0\)。固定 \(r\),用线段树维护区间最小值及个数,一段范围的最大和最小值会发生相同的改变,拿单调栈维护 \(mx,mn\) 即可。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=3e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n,a[N];
	struct Tree{
		int l,r,add;
		PII pre;
		inline void tag(int d){
			pre.first+=d;add+=d;
		}
		#define l(p) t[p].l
		#define r(p) t[p].r
		#define pre(p) t[p].pre
		#define add(p) t[p].add
	}t[N<<2];
	inline PII get(PII xx,PII yy){
		return xx.first==yy.first?make_pair(xx.first,xx.second+yy.second):min(xx,yy);
	}
	inline void up(int p){
		pre(p)=get(pre(p<<1),pre(p<<1|1));
	}
	inline void down(int p){
		if(add(p)) t[p<<1].tag(add(p)),t[p<<1|1].tag(add(p)),add(p)=0;
	}
	inline void Build(int p,int l,int r){
		l(p)=l,r(p)=r;
		if(l==r) return pre(p)={l,1},void();
		int mid=(l+r)>>1;
		Build(p<<1,l,mid);Build(p<<1|1,mid+1,r);
		up(p);
	}
	inline void update(int p,int l,int r,int d){
		if(l<=l(p) && r(p)<=r) return t[p].tag(d);
		down(p);
		int mid=(l(p)+r(p))>>1;
		if(l<=mid) update(p<<1,l,r,d);
		if(mid<r) update(p<<1|1,l,r,d);
		up(p);
	}
	inline PII query(int p,int l,int r){
		if(l<=l(p) && r(p)<=r) return pre(p);
		down(p);
		int mid=(l(p)+r(p))>>1;PII ans={1e9,0};
		if(l<=mid) ans=get(ans,query(p<<1,l,r));
		if(mid<r) ans=get(ans,query(p<<1|1,l,r));
		return ans;
	}
	int stk1[N],top1,stk2[N],top2;
	int main(){
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		Build(1,1,n);
		ll ans=0;
		for(int i=1;i<=n;i++){
			update(1,1,n,-1);
			while(top1 && a[stk1[top1]]<=a[i]){
				update(1,stk1[top1-1]+1,stk1[top1],a[i]-a[stk1[top1]]);
				top1--;
			}
			while(top2 && a[stk2[top2]]>=a[i]){
				update(1,stk2[top2-1]+1,stk2[top2],a[stk2[top2]]-a[i]);
				top2--;
			}
			stk1[++top1]=i;stk2[++top2]=i;
			PII tp=query(1,1,i);
			if(!tp.first) ans+=tp.second;
		}
		cout<<ans<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T3

初始有一个数组 \({1,2,…,n}\),其中 \(n\) 为偶数。

每次操作你可以选择相邻两个数 \(i,j\),把他们同时从数组中删去,费用是 \(c(i,j)\),其中 \(c\) 是给定的矩阵。

注意,两个数删除后,其位置不保留,也就是说他们两侧的数会变成相邻的。

\(n^2\) 次操作后,数组会被删空。定义总费用为所有操作的费用的最大值,你需要求出最小总费用。\(n\le 4000\)

二分答案 + 区间 dp + bitset优化

具体看代码的写法。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=4000+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H_READ{
	char Buf[1<<23],*P1=Buf,*P2=Buf;
	#define gc() (P1==P2&&(P2=(P1=Buf)+fread(Buf,1,1<<23,stdin),P1==P2)?EOF:*P1++)
	Tp inline void read(T &x){
		x=0;int ch=gc();bool f=0;
		while(!isdigit(ch)) f=ch=='-',ch=gc();
		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=gc();
		if(f) x=-x;
	}
	Tp inline void write(T x){
		if(x<0) putchar('-'),x=-x;
		if(x>9) putchar(x/10);putchar(x%10+'0');
	}
	Ts inline void read(T &x,args&... y){read(x),read(y...);}
	Ts inline void write(T x,args... y){write(x),putchar(' '),write(y...);puts("");}
}using H_H_READ::read;using H_H_READ::write;
namespace H_H{
	int n,c[N][N];
	bool w[N][N];
	bitset<N> f[N];
	bool check(int mid){
		MS(f,0);
		for(int i=1;i<=n;i++) f[i][i-1]=1;
		for(int l=n-1;l;l--){
			for(int k=l+1;k<=n;k+=2){
				if(f[l+1][k-1]&&!f[l][k]&&c[l][k]<=mid){
					f[l][k]=1;f[l]|=f[k+1]; 
//					for(int r=k+2;r<=n;r+=2) if(f[k+1][r]) f[l][r]=1;//暴力写法
				}
			}
		}
		return f[1][n];
	}
	int main(){
		read(n);
		MS(c,0x3f);
		for(int i=1;i<=n;i++){
			for(int j=i+1;j<=n;j+=2){
				read(c[i][j]);
				c[j][i]=c[i][j];
			}
		}
		int l=1,r=(n>>1)*(n>>1);
		while(l<r){
			int mid=(l+r)>>1;
			if(check(mid)) r=mid;
			else l=mid+1;
		}
		cout<<l<<"\n";
		return 0;
	}
}
int main(){
	H_H::main();
	return 0;	
}

T4

\(n\) 个矩形,求有多少个三元组满足对应的矩形不交 \(n\le 2\times 10^5\)

暴力没写完我是sb。

可以拿 bitset 优化到 \(\frac{n^3}{w}\)

11.21 NOIP 模拟赛 二一

0+100+80+50=230 pts。

已严肃完成今日 T1 写假,T3 没特判大学习。

T1

求是否存在长度为 \(n\) 的数组满足 \(x_1=A,x_n=B,\forall 1<i\le n,C≤|x_i−x_{i−1}|≤D\)

考虑前一部分为正增长,之后为负增长。枚举后判断一下即可。

Code

其实是贺别人的

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define Tp template<typename T>
#define Ts template<typename T,typename ...args>
Tp inline T lb(T x){return x&-x;}
using namespace std;
const int N=1e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n;
	ll A,B,C,D;
	int main(){
		cin>>n>>A>>B>>C>>D;
		for(int i=1;i<n;i++){
			ll L1=A+C*i,R1=A+D*i;
			ll L2=B+C*(n-i-1),R2=B+D*(n-i-1);
			if(L1>=L2&& L1<=R2 || L2>=L1&&L2<=R1) return puts("YES"),0;
		}
		puts("NO");
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}

T2

11.24 NOIP 模拟赛 二二

11.25 线下模拟赛

11.26 NOIP 模拟赛 二三

11.27 NOIP 模拟赛 二四

posted @ 2025-10-28 14:02  tyh_27  阅读(16)  评论(1)    收藏  举报