16 前两题

传奇挂分王之195pts再创新高 挂红温了

玩具谜题

模拟,可以展环为链(挂到只切了这道)

#include<bits/stdc++.h>
using namespace std;
int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){
		if(ch=='-') f=-1;
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}const int N=1e5+5;
bool face[N];char job[N][15];
int n,m,a,s,now,lst[N<<1];
int main(){
	freopen("toy.in","r",stdin);
	freopen("toy.out","w",stdout);
	n=read(),m=read();for(int i=1;i<=n;++i) lst[i]=i,lst[i+n]=i;
	for(int i=1;i<=n;++i) face[i]=read(),scanf("%s",job[i]+1);now=1;
	while(m--){
		a=read(),s=read();
		if(a^face[now]) now=lst[now+s];
		else now=lst[now+n-s];
	}printf("%s",job[now]+1);
	return 0;
}

组合数问题

预处理,直接模k,然后二维前缀和(二维前缀和挂了)

#include<bits/stdc++.h>
using namespace std;
int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){
		if(ch=='-') f=-1;
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
int t,k,n,m,res[2005][2005],rt[2005][2005];
int main(){
	freopen("problem.in","r",stdin);
	freopen("problem.out","w",stdout);
	t=read(),k=read();
	for(int i=0;i<=2002;++i) rt[i][0]=rt[i][i]=1;
	for(int i=1;i<=2002;++i){
		for(int j=1;j<=i;++j){
			rt[i][j]=(rt[i-1][j-1]+rt[i-1][j])%k;
			res[i][j]=res[i-1][j]-res[i-1][j-1]+res[i][j-1];
			if(rt[i][j]==0) ++res[i][j];
		}res[i][i+1]=res[i][i];
	}
	while(t--){
		n=read(),m=read();
		if(n<m) printf("%d\n",res[n][n]);
		else printf("%d\n",res[n][m]);
	}
	return 0;
}

天天爱跑步

dfs挂了
暴力代码

#include<bits/stdc++.h>
using namespace std;
int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){
		if(ch=='-') f=-1;
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
const int N=3e5+5;
int n,m,vis[N],ti[N],f[N][22],path[N];
int st,ed,dep[N],dfn[N],ans[N],mid,fl;
vector<int> tr[N];
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=20;i>=0;--i){
		if(dep[f[u][i]]>=dep[v]) u=f[u][i];
	}
	if(u==v) return u;
	for(int i=20;i>=0;--i){
		if(f[u][i]!=f[v][i]&&dep[u]==dep[v]) u=f[u][i],v=f[v][i];
	}
	return f[u][0];
}
void pre(int now,int fa){
	f[now][0]=fa;
	for(int i=1;i<=20;++i) f[now][i]=f[f[now][i-1]][i-1];
	dep[now]=dep[fa]+1;
	for(int to:tr[now]){
		if(to!=fa) pre(to,now);
	}
}
void work(int pos,int fr,int step){
	if(fl) return;
	if(fr==pos){path[step]=pos,dfn[pos]=dfn[path[step-1]]+1;
		for(int i=1;i<=step;++i){
			if(ti[path[i]]==dfn[path[i]]-1) ans[path[i]]++;
		}fl=1;return;
	}path[step]=pos,dfn[pos]=dfn[path[step-1]]+1;
	for(int to:tr[pos]){
		if(!vis[to]) vis[to]=1,work(to,fr,step+1);
	}path[step]=0,dfn[pos]=0;
}
int main(){
	freopen("running.in","r",stdin);
	freopen("running.out","w",stdout);
	n=read(),m=read();int a,b;
	for(int i=1;i<n;++i){
		scanf("%d %d",&a,&b);tr[a].push_back(b),tr[b].push_back(a);
	}dep[1]=1,pre(1,0);
	for(int i=1;i<=n;++i) ti[i]=read();
	while(m--){
		st=read(),ed=read(),mid=lca(st,ed);
		if(mid==st||mid==ed){
			fl=0;vis[st]=1;work(st,ed,1);
			memset(vis,0,sizeof(vis));memset(dfn,0,sizeof(dfn));
		}
		else{vis[st]=1;
			fl=0;work(st,mid,1);
			for(int i=1;i<=n;++i){
				if(dfn[i]) dfn[i]--;
			}
			fl=0;work(mid,ed,1);
			memset(dfn,0,sizeof(dfn));memset(vis,0,sizeof(vis));
		}
	}
	for(int i=1;i<=n;++i) printf("%d ",ans[i]);
	return 0;
}

谁爱调谁调,反正我不调

好像有什么线段树合并、启发式合并、动态开点权值线段树、Splay、树剖、树状数组、链表、二分、扫描线,但是我都不会

于是思考树上差分
首先路径可以表示为 slca(s,t)t
考虑如果在玩家的视角去看,会无法优化,所以站在观察者的视角(全局)去考虑什么样的玩家会对观察者做出贡献。
image

首先声明这篇题解的重点:

  1. 统计答案只和 s,t 有关,计算答案只和 i 有关。我们巧妙地通过值相等把答案存在桶里,然后用每个观察员去取它。理解这一点才能理解这一题!!!

  2. 设计一个顺序以统计、清空答案,使子树之间互不影响。这里一般正常的是 slcat 的顺序。

  3. 理解差分贡献。

我们讨论一下:

设观察员在 P 上。

一、如果 Pslca(s,t)
则当 dep[s]dep[p]=w[p] 时可以观察到,当然这个式子可以变成 dep[s]=w[p]+dep[p]

二、如果 Plca(s,t)t
dist[i,j] 表示 ij 的距离。则满足 dist[s,t]w[p]=dep[t]dep[p],即 dist[s,t]dep[t]=w[p]dep[p]

三、如果 Plca(s,t)
上面两个任意一个都可以处理,就用第一个吧……

注意到能对 P 做贡献的起点或终点一定都在以 P 为根的子树上,这使得可以在DFS回溯的过程中处理以任意节点为根的子树。(搬的)

现在我们思考统计子树贡献。

不能直接对于每个根求哪些点对 P 产生了贡献,因为一个点会对 P 以外的点做出贡献。

所以我们用一个桶记录答案。考虑一棵子树怎么统计答案,显然是在进入根节点之前和回溯回来以后桶内的差值。所以我们对于每个 P ,在进入每个子树前记一下桶里的值,回溯时处理。这就是第一篇题解所说的:以 P 为根递归整颗子树过程中在桶内产生的差值才是有效的。

考虑怎么累加答案

我们上面说到了,起点、终点满足怎么样的条件才能对 P 产生贡献。那么问题来了,我们怎么确定 P 的值呢?

其实考虑我们没有必要知道值,我们只需要在搜索时对于每个点都算一遍,以这个点可能产生的贡献对两个桶(上行和下行)进行更新。所以我们要记录以每个点为起点的路径的条数和以每个点为结尾的边。我们现在在树上 dfs ,所以我们知道的是 P ,所以我们利用 w[p]+dep[p]w[p]dep[p] 这两个只和 P 有关的式子进行更新。具体地,我们可以直接加上我们上面所说记录的以 w[p]+dep[p] 开头的边数和遍历所有以 w[p]dep[p] 结尾的边,去更新桶。

考虑如何清空子树贡献

我们可以在回溯时,利用LCA对所有开头和结尾的贡献值进行暴力还原。因此我们需要记录以每个点为lca的边。具体地,因为我们记录的是边 i ,所以起点是 bu1depsti ,而终点是 bu2disidepedi

注意事项

下行时可能为下标可能为负,所以整体加上N。

Code

#include<bits/stdc++.h>
using namespace std;
int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){
		if(ch=='-') f=-1;
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
const int N=3e5+5;
int n,m,ti[N],f[N][22],bu1[N<<1],bu2[N<<1];
int st[N],ed[N],dep[N],ans[N],dis[N],cnt[N];
vector<int> tr[N],t[N],LCA[N];
int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=20;i>=0;--i){
		if(dep[f[u][i]]>=dep[v]) u=f[u][i];
	}
	if(u==v) return u;
	for(int i=20;i>=0;--i){
		if(f[u][i]!=f[v][i]&&dep[u]==dep[v]) u=f[u][i],v=f[v][i];
	}
	return f[u][0];
}
void pre(int now,int fa){
	f[now][0]=fa;
	for(int i=1;i<=20;++i) f[now][i]=f[f[now][i-1]][i-1];
	dep[now]=dep[fa]+1;
	for(int to:tr[now]){
		if(to!=fa) pre(to,now);
	}
}
void dfs(int now){
    int a=bu1[ti[now]+dep[now]],b=bu2[ti[now]-dep[now]+N];
    for(int to:tr[now]){
        if(to!=f[now][0]) dfs(to);
    }bu1[dep[now]]+=cnt[now];
    for(int i:t[now]) bu2[dis[i]-dep[ed[i]]+N]++;
    ans[now]+=bu1[ti[now]+dep[now]]-a+bu2[ti[now]-dep[now]+N]-b;
    for(int i:LCA[now]) bu1[dep[st[i]]]--,bu2[dis[i]-dep[ed[i]]+N]--;
    return;
}
int main(){
	freopen("running.in","r",stdin);
	freopen("running.out","w",stdout);
	n=read(),m=read();int a,b;
	for(int i=1;i<n;++i){
		scanf("%d %d",&a,&b);tr[a].push_back(b),tr[b].push_back(a);
	}dep[1]=1,f[1][0]=1,pre(1,0);int mid;
	for(int i=1;i<=n;++i) ti[i]=read();
	for(int i=1;i<=m;++i){
		st[i]=read(),ed[i]=read(),mid=lca(st[i],ed[i]),dis[i]=dep[st[i]]+dep[ed[i]]-2*dep[mid];
		cnt[st[i]]++;t[ed[i]].push_back(i);LCA[mid].push_back(i);
        if(dep[mid]+ti[mid]==dep[st[i]]) --ans[mid];
	}dfs(1);
	for(int i=1;i<=n;++i) printf("%d ",ans[i]);
	return 0;
}

蚯蚓

没排序
考虑砍掉的蚯蚓是单调递减的,所以三个单调队列维护一下原序列,砍掉的前半部分,砍掉的后半部分,记录一下偏移量,就做完了。
证明看 dbxxx

#include<bits/stdc++.h>
using namespace std;
int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){
		if(ch=='-') f=-1;
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}queue<long long> a,b,c;
const int N=7e6+5;
const long long inf=1e15;
int n,m,p,u,v,t,po,d[N];
long long cut,cut1,cut2;
bool cmp(int a,int b){
	return a>b;
}
int main(){
	freopen("earthworm.in","r",stdin);
	freopen("earthworm.out","w",stdout);
	n=read(),m=read(),p=read(),u=read(),v=read(),t=read();
	for(int i=1;i<=n;++i) d[i]=read();
	sort(d+1,d+n+1,cmp);//我造
	for(int i=1;i<=n;++i) a.push(d[i]);
	for(int i=1;i<=m;++i){
		cut=-inf;if(!a.empty()) cut=a.front(),po=1;
		if(!b.empty()){if(cut<b.front()) cut=b.front(),po=2;}
		if(!c.empty()){if(cut<c.front()) cut=c.front(),po=3;}
		if(po==1) a.pop();else if(po==2) b.pop();else c.pop();
		cut+=i*p-p;cut1=cut*u/v,cut2=cut-cut1;cut1-=i*p,cut2-=i*p;
		b.push(cut1),c.push(cut2);
		if(i%t==0) printf("%lld ",cut);
	}printf("\n");
	for(int i=1;i<=n+m;++i){
		cut=-inf;if(!a.empty()) cut=a.front(),po=1;
		if(!b.empty()){if(cut<b.front()) cut=b.front(),po=2;}
		if(!c.empty()){if(cut<c.front()) cut=c.front(),po=3;}
		if(po==1) a.pop();else if(po==2) b.pop();else c.pop();
		if(i%t==0) printf("%lld ",cut+m*p);
	}
	return 0;
}
posted @   mountzhu  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示