NOIP2022 题解

终于有机会补NOIP的题了

T1

考虑枚举 C 与 F 的纵列
考虑预处理出每个点最左边和最下边可以延伸到哪
之后枚举列,然后对行做类似于扫描线的操作,统计有多少可行的 "第一横行",然后把当前的行钦定为 "第二横行",相乘贡献进答案
对于 F 要额外乘上向下延伸的可能方案
时间复杂度 Θ(Tnm)

Code
#include<bits/stdc++.h>
#define int long long
#define reg register
using namespace std;
const int N=1e3+10,mod=998244353;
int n,m,c,f;
long long ans1,ans2;
int len[N][N],down[N][N];
char a[N][N];
signed main(){
	reg int _,id; scanf("%lld%lld",&_,&id);
	while (_--){
		ans1=ans2=0;
		scanf("%lld%lld%lld%lld",&n,&m,&c,&f); 
		for (reg int i=1;i<=n;i++) scanf("%s",a[i]+1);
		for (reg int i=1;i<=n+1;i++) for (reg int j=1;j<=m+1;j++) len[i][j]=down[i][j]=0;
		for (reg int j=m;j;j--) for (reg int i=1;i<=n;i++) if (a[i][j]=='0') len[i][j]=len[i][j+1]+1;
		for (reg int i=n;i;i--) for (reg int j=1;j<=m;j++) if (a[i][j]=='0') down[i][j]=down[i+1][j]+1;
	//	for (reg int i=1;i<=n;i++,puts("")) for (reg int j=1;j<=m;j++) cout<<len[i][j]<<" ";
		for (reg int j=1;j<=m;j++){
			reg long long sum=0,cnt=0;
		    for (reg int i=1;i<=n;i++) if (a[i][j]=='1') cnt=sum=0; else{
				ans1=(ans1+1ll*(len[i][j]-1)*sum%mod)%mod;
				if (cnt) sum+=len[i-1][j]-1,sum%=mod; cnt++;
			}
		}
		for (reg int j=1;j<=m;j++){
			reg long long sum=0,cnt=0;
		    for (reg int i=1;i<=n;i++) if (a[i][j]=='1') cnt=sum=0; else{
				ans2=(ans2+1ll*(len[i][j]-1)*sum%mod*(down[i][j]-1)%mod)%mod;
				if (cnt) sum+=len[i-1][j]-1,sum%=mod; cnt++;
			}
		}
		printf("%lld %lld\n",(ans1*c%mod+mod)%mod,(ans2*f%mod+mod)%mod);
	}
	return 0;
}

T2

约定:记位置 i 之后出现同色位置为 gi(A1,A2,A3An) 表示从栈顶到栈底分别为 An,An1A1 的栈
首先考虑 k=2n2 的时候怎么做
可以让 n1 个栈每个放两种颜色,剩下一个空栈用来消除其它栈的栈底部
考虑怎么拓展到 k=2n1
考虑最极端的情况,当前 n1 个栈都填满了两个颜色,接下来要填一个新颜色
这个颜色有两种选择,可以放在某个栈的顶部,那么那个 3 元栈中间就消不掉,或者放在空栈,那么剩下元素的栈底就消不掉
发现能不堵在空栈就不堵在空栈,所以考虑找到一个栈 (A,B),s.t.gB>gA,那么在消除 B 之前 A 已经被消除了,B 的消除不会受到影响,这就解决了 (A,B,C)B 元素的消除
如果没有找到这样一个栈,那么所有的栈顶一定在栈底被消除之前消除,则可以把当前的元素放在空栈中
注意到如果按照后一种方式消除,会产生一下问题 (A,B) 被替换为 (A,c),但此时 gC>gA 会导致不能消除,于是在选择单元栈时贪心选择对应 g 最大一个。
总结一下过程:首先如果空栈个数大于 1,那么直接选择;接下来如果有单元栈,选择元素对应 g 最大的一个。若都没有,选择 gB>gA(A,B),最后实在没有选空栈
这个过程开三个 set 维护,分别为元素个数为 0,1,2。 时间复杂度 Θ(Slogn)

Code
#include<bits/stdc++.h>
#define reg register
using namespace std;
inline int read(){
	int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') k=-1; ch=getchar();}
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;} 
inline int cmax(reg int x,reg int y){return x>y?x:y;}
inline int cabs(reg int x){return x<0?-x:x;}
const int N=2e6+10,M=1e4+10,mod=1e9+7;
int debug=0;
struct Query{int op,x,y;}ans[N<<1];
struct Node{int op,x;inline bool operator<(const Node &rhs)const{return op==rhs.op?x>rhs.x:op>rhs.op;}};
int n,m,k,cnt,g[N],vis[N],s[M][4],top[M],bel[N],a[N];
vector<int> v[M];   set<Node> q1,q2,q0;
inline void work(reg int x,reg int y){if (y) ans[++cnt]=(Query){2,x,y}; else ans[++cnt]=(Query){1,x,0};}
inline void Ins(reg int id,reg int x){bel[x]=id;s[id][++top[id]]=x,work(id,0);}
inline void solve(){
	for (reg int i=1;i<=m;i++) v[a[i]].push_back(i);
	for (reg int i=1;i<=n;i++) q0.insert((Node){0,i});
	for (reg int i=1;i<=k;i++) for (reg int j=0;j<v[i].size();j+=2) g[v[i][j]]=v[i][j+1],g[v[i][j+1]]=v[i][j],vis[v[i][j]]=1;
	for (reg int i=1;i<=m;i++){
		if (vis[i]){ 
			if (q0.size()>1){
				auto it=*q0.begin();
				q0.erase(it); Ins(it.x,i);
				q1.insert((Node){g[i],it.x});
			}else if (!q1.empty()){
				auto it=*q1.begin();
				q1.erase(it); Ins(it.x,i);
				q2.insert((Node){g[i]>it.op,it.x});		
			}else{
				if ((*q2.begin()).op){  
					auto it=*q2.begin();
					q2.erase(it); Ins(it.x,i);
				}else{
					auto it=*q0.begin();
					q0.erase(it); Ins(it.x,i);
					q1.insert((Node){g[i],it.x});
				}
			}
		}else{ 
			reg int now=bel[g[i]];
			if (s[now][top[now]]==g[i]){
				work(now,0); 
				if (top[now]==1) q1.erase((Node){g[s[now][1]],now});
				else if (top[now]==2) q2.erase((Node){g[s[now][2]]>g[s[now][1]],now});
				top[now]--;
				if (top[now]==0) q0.insert((Node){0,now});
				else if (top[now]==1) q1.insert((Node){g[s[now][1]],now});
				else if (top[now]==2) q2.insert((Node){g[s[now][2]]>g[s[now][1]],now});
			}else{
				reg int emp=(*q0.begin()).x;
				work(emp,0),work(emp,now);
				if (top[now]==1) q1.erase((Node){g[s[now][1]],now});
				else if (top[now]==2) q2.erase((Node){g[s[now][2]]>g[s[now][1]],now});
				for (reg int i=1;i<top[now];i++) s[now][i]=s[now][i+1];
				top[now]--;
				if (top[now]==0) q0.insert((Node){0,now});
				else if (top[now]==1) q1.insert((Node){g[s[now][1]],now});
				else if (top[now]==2) q2.insert((Node){g[s[now][2]]>g[s[now][1]],now});
			}
		}	
	}
	printf("%lld\n",cnt);
	for (reg int i=1;i<=cnt;i++) if (ans[i].op==1) printf("1 %d\n",ans[i].x); else printf("2 %d %d\n",ans[i].x,ans[i].y); 
}
inline void clear(){
	q0.clear();q1.clear();q2.clear();   cnt=0;
	for (reg int i=1;i<=k;i++) v[i].clear();
	for (reg int i=1;i<=n;i++) top[i]=0;
	for (reg int i=1;i<=m;i++) g[i]=bel[i]=vis[i]=0; 
} 
signed main(){
	reg int _=read(); 
	while (_--){
		n=read(),m=read(),k=read(); for (reg int i=1;i<=m;i++) a[i]=read();
		clear(); solve();
	}
	return 0;
}



T3

首先考虑将边双缩点,边双内部的边可以任意选择选或不选。
接下来考虑树型 dp。定义 dpu,1/0 表示以 u 为根的子树中有没有关键点的方案数,转移如下,记:

A=vson{u}2fv,0+fv,1

fu,0=vson{u}fv,0

fu,1=(Afu,0)(2szu)+fu,0(2szu1)

其中 szu 表示 u 代表的边双大小
考虑如何统计答案,我们在每个节点 u 钦定关键点只存在于 u 子树中

Ans=u=1nfu,1(2totsizeu+1)

其中 tot 表示边双个数,sizeu 表示缩点后 u 的子树大小
特别地,为了避免算重。每次统计答案时钦定边 fauu 不选择

Code
#include<bits/stdc++.h>
#define int long long
#define reg register
using namespace std;
inline int read(){
	int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') k=-1; ch=getchar();}
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;}
inline int cmax(reg int x,reg int y){return x>y?x:y;}
const int N=1e6+10,mod=1e9+7;
int n,m,head[N],cnt,ky[N];
struct EDGE{int pre,to;}edge[N<<1];
inline void add(reg int u,reg int v){edge[++cnt]=(EDGE){head[u],v},head[u]=cnt;}
int dfn[N],dfc,low[N],vis[N],s[N],top,tot,bel[N],sz[N],pw[N];
inline void tarjan(reg int u,reg int fa){
	dfn[u]=low[u]=++dfc;vis[u]=1,s[++top]=u; 
	for (reg int i=head[u],v;v=edge[i].to,i;i=edge[i].pre) if (v^fa){
		if (!dfn[v]) tarjan(v,u),low[u]=cmin(low[u],low[v]);
		else if (vis[v]) low[u]=cmin(low[u],dfn[v]);		
	}
	if (low[u]==dfn[u]){
		tot++; reg int v;
		while (v=s[top--]){bel[v]=tot,sz[tot]++;if (v==u) break;}
	}
}
vector<int> G[N];
int ans,f[N][2],size[N];
inline void dfs(reg int u,reg int fa){ size[u]=f[u][0]=f[u][1]=1;
	for (auto v:G[u]) if (v^fa){ dfs(v,u); size[u]+=size[v];
		f[u][1]=f[u][1]*((2*f[v][0]%mod+f[v][1])%mod)%mod;
		f[u][0]=f[u][0]*f[v][0]%mod*2%mod;	
	} f[u][1]-=f[u][0],f[u][1]%mod; f[u][1]=(f[u][1]*pw[sz[u]]%mod+f[u][0]*(pw[sz[u]]-1)%mod)%mod; 
	ans+=f[u][1]*(u==1?pw[0]:pw[tot-1-size[u]]),ans%=mod;
}
int u[N],v[N];
signed main(){
	n=read(),m=read(); pw[0]=1;
	for (reg int i=1;i<=cmax(n,m);i++) pw[i]=pw[i-1]*2%mod;
	for (reg int i=1;i<=m;i++) u[i]=read(),v[i]=read(),add(u[i],v[i]),add(v[i],u[i]);
	tarjan(1,0); 
	for (reg int i=1;i<=m;i++) if (bel[u[i]]!=bel[v[i]]) G[bel[u[i]]].push_back(bel[v[i]]),G[bel[v[i]]].push_back(bel[u[i]]); 
	for (reg int i=1;i<=tot;i++){sort(G[i].begin(),G[i].end());G[i].erase(unique(G[i].begin(),G[i].end()),G[i].end());} dfs(1,0);
	printf("%lld",(ans*pw[m-(tot-1)]%mod+mod)%mod);
	return 0;
}

T4

首先从一类问题引入;区间历史和
考虑这么一个问题,记 S(l,r,t) 表示 t 时刻 [l,r] 的和
支持区间修改,维护任意 mi=1mS(l,r,i)

Ai 表示数组元素,Bi 表示区间历史和(不包括当前操作)
考虑定义一个辅助数组 Ci=BitAi
发现若 Ai 没有修改,那么 Ci 没有变化,若 Ai 修改,则
ΔCi=(Bi+Ai+x)(t+1)(Ai+x)=txtΔAi
这样就将问题转为区间加减与区间和查询的问题了

类似地,我们可以定义 Si=AiBiAnsi 为历史和,Ci 的定义也是类似的
首先我们将所有的询问离线,按右端点排序。使用扫描线,此时的 "时刻" 变为当前的右端点。用线段树维护 [l,r] 的区间历史和。
考虑扫描线从 r1 变成 r 时带来的影响:有一段的区间最大值变成了 ar(br),剩下的不变。改变的区间容易用单调栈维护,问题变成了区间覆盖问题
考虑查询操作怎么实现,在线段树上维护区间的 C,A,则答案为 C+(t+1)S

接下来只需要维护区间信息与标记的合并与 lazytag 的合并即可
考虑 Si 的影响因素有哪些,不难想到定义线段树信息

[SABClen]

其中 len 是区间长度,转移矩阵是:

[[ca=cb=0]00tags0[a=0]b[a=0]0taga0[b=0]a0[b=0]tagb000010ababtaglen1]

其中 a,b 为区间覆盖后的元素,若为 0 则无变化
tags,taga,tagb,taglen 为标记
之后考虑 lazytag 合并,考虑一次修改后的变化如下:

CC+tagsS+tagaA+tagbB+taglenlen1

A[a=0]A+alen2

B[b=0]B+blen3

S[a=b=0]S+[a=0]bA+[b=0]aB+ablen4

再进行一次修改操作,考虑在保存了之间 C 的变化下的新增量
将上边 2,3,4 式带入 1 ,令 S,A,B,C,len 为未知量,得到系数

[ΔtagsΔtagaΔtagbΔtaglen1]

[[a=b=0][a=0]b[b=0]aab00[a=0]0a000[b=0]b000010tagstagatagbtaglen1]

最后考虑修改时的矩阵是什么

ΔC=ΔAnsΔ(tS)=ΔAnsΔ(tS)

展开得到 ΔC=ΔtS(t+Δt)(S+ΔS)+tS
化简得到 ΔC=tΔS

最后考虑覆盖传入的标记是什么,根据上面推出的式子,得到
a=v,b=0,tags=t,taga=0,tagb=vt,taglen=0 其中 v 为覆盖 A 的量,数组 B 的修改是类似的

Code
#include<bits/stdc++.h>
#define reg register
#define int long long
using namespace std;
inline int read(){
	int k=1,x=0;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') k=-1; ch=getchar();}
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
	return k*x;
}
inline int cmin(reg int x,reg int y){return x<y?x:y;} 
inline int cmax(reg int x,reg int y){return x>y?x:y;}
inline int cabs(reg int x){return x<0?-x:x;}
const int N=25e4+10,INF=1e9;
int n,m;
struct Query{int l,id;};
vector<Query> v[N];
#define ull unsigned long long
struct Node{
	ull ca,cb,tags,taga,tagb,taglen;
	inline bool empty(){return !ca&&!cb&&!tags&&!taga&&!tagb&&!taglen;}
	inline void clear(){ca=cb=tags=taga=tagb=taglen=0;}
	inline void merge(const Node &rhs){
		if (empty()) return (void)(*this=rhs);
		reg int tg1,tg2,tg3,tg4;		
		tg1=(!ca&&!cb)*rhs.tags+tags;
		tg2=rhs.tags*(!ca)*cb+rhs.taga*(!ca)+taga;
		tg3=rhs.tags*(!cb)*ca+rhs.tagb*(!cb)+tagb;
		tg4=rhs.tags*ca*cb+rhs.taga*ca+rhs.tagb*cb+rhs.taglen+taglen;
		tags=tg1,taga=tg2,tagb=tg3,taglen=tg4;
		if (rhs.ca) ca=rhs.ca; if (rhs.cb) cb=rhs.cb; 
	}	
};
struct tree{ull sum,A,B,C;Node tag;}t[N<<2];
inline void brush(reg int p,reg int l,reg int r,Node k){reg int len=r-l+1;
	t[p].C+=k.taga*t[p].A+k.tagb*t[p].B+k.taglen*len+k.tags*t[p].sum;
	t[p].sum=(!k.ca&&!k.cb)*t[p].sum+(!k.ca)*k.cb*t[p].A+(!k.cb)*k.ca*t[p].B+k.ca*k.cb*len;
	t[p].A=(!k.ca)*t[p].A+k.ca*len; t[p].B=(!k.cb)*t[p].B+k.cb*len; t[p].tag.merge(k);
}
inline void pushdown(reg int p,reg int l,reg int r){
	if (t[p].tag.empty()) return; reg int mid=l+r>>1; brush(p<<1,l,mid,t[p].tag);
	brush(p<<1|1,mid+1,r,t[p].tag); t[p].tag.clear();
}
inline void pushup(reg int p){
	t[p].A=t[p<<1].A+t[p<<1|1].A;t[p].B=t[p<<1].B+t[p<<1|1].B;
	t[p].C=t[p<<1].C+t[p<<1|1].C;t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
}
inline void modify(reg int p,reg int l,reg int r,reg int L,reg int R,reg Node k){
	if (L<=l&&r<=R) return brush(p,l,r,k); reg int mid=l+r>>1; pushdown(p,l,r);
	if (L<=mid) modify(p<<1,l,mid,L,R,k); if (R>mid) modify(p<<1|1,mid+1,r,L,R,k); pushup(p); 
}
inline ull query(reg int p,reg int l,reg int r,reg int L,reg int R,reg int tim){
	if (L<=l&&r<=R) return t[p].sum*(ull)(tim+1)+t[p].C; reg int mid=l+r>>1;reg ull res=0; pushdown(p,l,r);
	if (L<=mid) res+=query(p<<1,l,mid,L,R,tim); if (R>mid) res+=query(p<<1|1,mid+1,r,L,R,tim); return res;
}
int s1[N],s2[N],top1,top2,a[N],b[N];
ull ans[N];
signed main(){//freopen(".in.txt","r",stdin);
	reg int _=read();n=read();
	for (reg int i=1;i<=n;i++) a[i]=read();for (reg int i=1;i<=n;i++) b[i]=read();
	m=read(); for (reg int i=1,l,r;i<=m;i++) l=read(),r=read(),v[r].push_back((Query){l,i});
	for (reg int i=1;i<=n;i++){
		while (top1&&a[s1[top1]]<a[i]) top1--; while (top2&&b[s2[top2]]<b[i]) top2--;
		modify(1,1,n,s1[top1]+1,i,(Node){a[i],0,i,0,-(ull)i*(ull)a[i],0});
		modify(1,1,n,s2[top2]+1,i,(Node){0,b[i],i,-(ull)i*(ull)b[i],0,0}); 
		s1[++top1]=i,s2[++top2]=i; for (auto it:v[i]) ans[it.id]=query(1,1,n,it.l,i,i);
	}
	for (reg int i=1;i<=m;i++) printf("%llu\n",ans[i]);
	return 0;
}



posted @   Matutino_Lux  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示