既然选择了远方,便只顾风|

H_W_Y

园龄:1年11个月粉丝:28关注:15

2023-12-20 17:22阅读: 42评论: 0推荐: 0

NOIP2022 sol

20231215

NOIP2022 sol + 4道杂题

A. [NOIP2022] 种花

[NOIP2022] 种花

小 C 决定在他的花园里种出 CCF 字样的图案,因此他想知道 CF 两个字母各自有多少种种花的方案;不幸的是,花园中有一些土坑,这些位置无法种花,因此他希望你能帮助他解决这个问题。

花园可以看作有 n×m 个位置的网格图,从上到下分别为第 1 到第 n 行,从左到右分别为第 1 列到第 m 列,其中每个位置有可能是土坑,也有可能不是,可以用 ai,j=1 表示第 i 行第 j 列这个位置有土坑,否则用 ai,j=0 表示这个位置没土坑。

一种种花方案被称为 C- 的,如果存在 x1,x2[1,n],以及 y0,y1,y2[1,m],满足 x1+1<x2,并且 y0<y1,y2m,使得第 x1 的第 y0 到第 y1 、第 x2 的第 y0 到第 y2 以及第 y0 的第 x1 到第 x2 不为土坑,且只在上述这些位置上种花。

一种种花方案被称为 F- 的,如果存在 x1,x2,x3[1,n],以及 y0,y1,y2[1,m],满足 x1+1<x2<x3,并且 y0<y1,y2m,使得第 x1 的第 y0 到第 y1 、第 x2 的第 y0 到第 y2 以及第 y0 的第 x1 到第 x3 不为土坑,且只在上述这些位置上种花。

样例一解释中给出了 C- 形和 F- 形种花方案的图案示例。

现在小 C 想知道,给定 n,m 以及表示每个位置是否为土坑的值 {ai,j}C- 形和 F- 形种花方案分别有多少种可能?由于答案可能非常之大,你只需要输出其对 998244353 取模的结果即可,具体输出结果请看输出格式部分。

1T51n,m1030c,f1ai,j{0,1}

直接模拟即可。

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int N=1e3+5;
const ll mod=998244353;
int nw,n,m,c,f,s[N][N],sc[N],sf[N],T,id;
ll ansc=0,ansf=0;
char a[N][N];

int main(){
  /*2023.12.15 H_W_Y P8865 [NOIP2022] 种花 乱搞*/
  scanf("%d%d",&T,&id);
  while(T--){
    scanf("%d%d%d%d",&n,&m,&c,&f); 
    memset(s,0,sizeof(s));
    memset(sc,0,sizeof(sc));
    memset(sf,0,sizeof(sf));
    ansc=ansf=0;
    
    for(int i=1;i<=n;i++){
      scanf("%s",a[i]+1);
      for(int j=m;j>=1;j--){
        if(a[i][j]=='0') s[i][j]=s[i][j+1]+1;
        else s[i][j]=0;
      }
    }
    
    for(int j=1;j<=m;nw=0,j++){
  	  for(int i=n;i>=1;i--){
  	    if(a[i][j]=='0') sc[i]=sc[i+1]+s[i][j]-1,sf[i]=sf[i+1]+(s[i][j]-1)*nw,++nw;
  	    else nw=0,sc[i]=sf[i]=0;
  	  }
  	  for(int i=1;i+2<=n;i++){
  	    if(a[i][j]=='1'||a[i+1][j]=='1') continue;
  	    ansc=(ansc+1ll*(s[i][j]-1)*sc[i+2]%mod)%mod;
  	    ansf=(ansf+1ll*(s[i][j]-1)*sf[i+2]%mod)%mod;
  	  }
    }
    ansc*=c;ansf*=f;
    printf("%lld %lld\n",ansc,ansf);
  }
  return 0;
}

B. [NOIP2022] 喵了个喵

[NOIP2022] 喵了个喵

小 E 喜欢上了一款叫做《喵了个喵》的游戏。这个游戏有一个牌堆和 n 个可以从栈底删除元素的栈,任务是要通过游戏规则将所有的卡牌消去。开始时牌堆中有 m 张卡牌,从上到下的图案分别是 a1,a2,,am。所有的卡牌一共有 k 种图案,从 1k 编号。牌堆中每一种图案的卡牌都有偶数张。开始时所有的栈都是空的。这个游戏有两种操作:

  • 选择一个栈,将牌堆顶上的卡牌放入栈的顶部。如果这么操作后,这个栈最上方的两张牌有相同的图案,则会自动将这两张牌消去。
  • 选择两个不同的栈,如果这两个栈栈的卡牌有相同的图案,则可以将这两张牌消去,原来在栈底上方的卡牌会成为新的栈底。如果不同,则什么也不会做。

这个游戏一共有 T 关,小 E 一直无法通关。请你帮小 E 设计一下游戏方案,即对于游戏的每一关,给出相应的操作序列使得小 E 可以把所有的卡牌消去。

测试点 T= n k= m
13 1001 300 2n2 无限制
46 1002 =2 2n1 无限制
710 3 =3 2n1 14
1114 1004 =3 2n1 无限制
1520 1005 300 2n1 无限制

这个表挺有意思的啊。好一道构造题。


首先,容易发现我们的 k 只有两种取值,为什么呢?

因为小了太简单了,而大了根本做不了,

所以我们不妨从 k=2n2 这个最小的入手。


发现这种情况是简单的,也就是我们将前 n1 个栈,一个栈放两个,

上面一个下面一个,容易证明,每一种只有一个。

那么这样我们会多出来一个栈,我们把它叫做 特殊栈

发现有了这个栈之后就很好处理了。

假设当前进来一个 i 种类的,

如果它之前没有出现过,那么直接放到一个有空位的栈里面去就可以了。

而如果它在一个栈的上面,我们直接放上去抵消掉即可,

反之我们就把它放在 特殊栈 里面,利用特殊栈把它消掉。

这样问题就可以轻松解决。


现在再来考虑多一个的情况,

那么它也一定是把所有的栈都满了的时候才会出现。

假设现在已经放满了,当前又进来了一个新的种类 w,我们希望知道他能放在哪里?

首先需要找到下一个栈底元素进来的位置,因为这个时候消掉那个栈底元素会用到特殊栈,

我们假设这个栈是 [v,u] 表示它上面是 v,下面是 u,那么这个 w 的位置只会影响到这一个栈,因为 u 下一个出现最早。

于是有以下几种情况:

  1. 下一个 w 在下一个 u 之前,那么这个特殊栈随便用,之间把 w 放到特殊栈里面去。
  2. u<v<w,即下一个 u 最先出现(这里的 < 是下一个的出现时间),那么我们直接把 w 叠在 v 上面,因为 u 很快就会走了。
  3. v<u<w,那么我们不能把 w 放到 v 上面,因为 v 很快就要走了,而在 u 出现之前我们又不会用到特殊栈,所以我们直接把 w 放到特殊栈里面。于是这个时候就出现了一个 换栈 的过程,即全局的特殊栈现在变成了 [v,u] 所在的栈,容易发现这样不会错。

这样我们就分析完了,具体实现的时候也就是直接模拟,

找下一个出现位置也可以直接找,因为我们一定不会重复找一段区间。

我们直接维护一下特殊栈是哪个栈,那些栈有空位即可。

#include <bits/stdc++.h>
using namespace std;
#define pb push_back

const int M=2e6+5;
int n,m,k,a[M],h[605],pos,T;
vector<deque<int> > s(305);
struct node{int op,x,y;};
vector<node> ans;
queue<int> q;

void init(){
  for(int i=1;i<=k;i++) h[i]=0;
  while(!q.empty()) q.pop();
  ans.clear();pos=0;
}

void oper1(int x,int v){
  ans.pb((node){1,x,0});
  if(s[x].size()&&s[x].back()==v){
  	s[x].pop_back();
  	if(x!=pos&&s[x].size()<2) q.push(x);
  	h[v]=0;
  }else s[x].pb(v),h[v]=x;
}

void oper2(int x,int y){
  ans.pb((node){2,x,y});
  h[s[x][0]]=0;
  s[x].pop_front();s[y].pop_front();
  if(x!=pos&&s[x].size()<2) q.push(x);
}

int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  /*2023.12.15 H_W_Y P8866 [NOIP2022] 喵了个喵 构造*/ 
  cin>>T;
  while(T--){
  	cin>>n>>m>>k;
  	init();
  	for(int i=1;i<=m;i++) cin>>a[i];
  	
  	pos=n;
  	for(int i=1;i<n;i++) q.push(i),q.push(i); 
  	for(int i=1;i<=m;i++){
  	  int x=h[a[i]];
	  if(x){
	  	if(s[x].back()==a[i]) oper1(x,a[i]);
		else oper1(pos,a[i]),oper2(x,pos);	
	    continue;
	  }
	  if(!q.empty()){
		x=q.front();q.pop();
		oper1(x,a[i]);
		continue;
	  }
	  for(int j=i+1;j<=m;j++){
	    if(a[j]==a[i]) break;
		else if(h[a[j]]&&s[h[a[j]]][0]==a[j]){x=h[a[j]];break;} 
	  }	  
	  if(x==0) oper1(pos,a[i]);
	  else{
		int fi=s[x].front(),se=s[x].back();
		for(int j=i+1;j<=m;j++){
		  if(a[j]==fi){oper1(x,a[i]);break;}
		  else if(a[j]==se){
			oper1(pos,a[i]);q.push(pos);
			pos=x;break;
		  }	
		}
      }
	}
  	
  	cout<<ans.size()<<'\n';
  	for(auto i:ans){
  	  if(i.op==1) cout<<1<<" "<<i.x<<'\n';
	  else cout<<2<<' '<<i.x<<' '<<i.y<<'\n';	
	}
  }
  return 0;
}

C. [NOIP2022] 建造军营

[NOIP2022] 建造军营

A 国与 B 国正在激烈交战中,A 国打算在自己的国土上建造一些军营。

A 国的国土由 n 座城市组成,m 条双向道路连接这些城市,使得任意两座城市均可通过道路直接或间接到达。A 国打算选择一座或多座城市(至少一座),并在这些城市上各建造一座军营。

众所周知,军营之间的联络是十分重要的。然而此时 A 国接到情报,B 国将会于不久后袭击 A 国的一条道路,但具体的袭击目标却无从得知。如果 B 国袭击成功,这条道路将被切断,可能会造成 A 国某两个军营无法互相到达,这是 A 国极力避免的。因此 A 国决定派兵看守若干条道路(可以是一条或多条,也可以一条也不看守),A 国有信心保证被派兵看守的道路能够抵御 B 国的袭击而不被切断。

A 国希望制定一个建造军营和看守道路的方案,使得 B 国袭击的无论是 A 国的哪条道路,都不会造成某两座军营无法互相到达。现在,请你帮 A 国计算一下可能的建造军营和看守道路的方案数共有多少。由于方案数可能会很多,你只需要输出其对 1,000,000,007(109+7) 取模的值即可。两个方案被认为是不同的,当且仅当存在至少一 座城市在一个方案中建造了军营而在另一个方案中没有,或者存在至少一条道路在一个 方案中被派兵看守而在另一个方案中没有。

1n5×105n1m1061ui,vinuivi

首先,很容易发现,这个东西就是让你先缩点,留下来桥的。


于是我们就得到了一棵树,

这些树边的选择是有要求的,而每一个边双里面的边是可选可不选的,也就是有 2au 种情况,

而这里的 au 是一个边双的大小。


剩下来的这个计数问题就变成了一个树形 dp 了,

发现选不选桥其实只是和你选没选这个子树中的节点有关,于是我们记 fu 表示选了 u 子树中的点的方案数,

记录答案的时候就是你选的这些点的 LCA 是 u 的情况对答案的贡献。

而这个转移是平凡的,

直接枚举一下 v 加上答案,而注意要排除一个都没选的情况,记录答案的时候要减去 LCA 不是 u 的贡献。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second

const ll mod=1e9+7;
const int N=1e6+5;
int n,m,sz[N],a[N],bl[N],scc=0,st[N<<2],tp=0,dfn[N],low[N],idx=0;
ll f[N],t[N],ans=0;
vector<int> G[N];
vector<pii> g[N];

void tarjan(int u,int k){
  dfn[u]=low[u]=++idx;st[++tp]=u;
  for(auto i:g[u]){
  	int v=i.fi;
  	if(!dfn[v]) tarjan(v,i.se),low[u]=min(low[u],low[v]);
  	else if(i.se!=k) low[u]=min(low[u],dfn[v]);
  }
  if(low[u]==dfn[u]){
  	++scc;int x;
    while((x=st[tp])){
      bl[x]=scc;--tp;
      if(x==u) break;
    }
  }
}

void init(){
  t[0]=1ll;
  for(int i=1;i<N;i++) t[i]=(t[i-1]+t[i-1])%mod;
}

void dfs(int u,int fa){
  sz[u]=1;f[u]=t[a[u]];
  for(auto v:G[u]){
  	if(v==fa) continue;
  	dfs(v,u);
  	sz[u]+=sz[v];
  	f[u]=f[u]*(t[sz[v]]+f[v])%mod;
  } 
  f[u]=(f[u]-t[sz[u]-1]+mod)%mod;
  ll s=f[u];
  for(auto v:G[u]){
  	if(v==fa) continue;
  	s=(s-f[v]*t[sz[u]-sz[v]-1]%mod+mod)%mod;
  }
  ans=(ans+s*t[scc-sz[u]]%mod)%mod;
}

int main(){
  /*2023.12.20 H_W_Y P8867 [NOIP2022] 建造军营 Tarjan + 树形 dp*/
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n>>m;init();
  for(int i=1,u,v;i<=m;i++) cin>>u>>v,g[u].pb({v,i}),g[v].pb({u,i});
  for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1);
  for(int u=1;u<=n;u++){
  	++a[bl[u]];
  	for(auto i:g[u]){
  	  int v=i.fi;
  	  if(bl[v]!=bl[u]) G[bl[u]].pb(bl[v]);
  	}
  }
  dfs(1,0);
  ans=ans*t[m-scc+1]%mod;
  cout<<ans<<'\n';
  return 0;
}

D. [NOIP2022] 比赛

[NOIP2022] 比赛

给定 A1,2,,n,B1,2,,n,以及 m 个询问,每次询问给出 l,r,求

p=lrq=pr(maxi=pqAi)(maxi=pqBi)

1n,m2.5×105

区间子区间问题典。


区间子区间问题的常规处理方法已经在 lxlds 中讲的很清楚了,

这道题的 max 我们同样可以用单调栈维护出来,分析一下去加信息累加时的标记。


其实题解部分已经讲得很清楚了

主要的思想就是先将同类的标记合并之后,我们对于交错的标记再进行一个讨论就可以了,

也就是分组,累加时分类讨论。具体看代码吧。


代码中我们钦定一个标记组是先历史值累加再赋值。

#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back

const int N=1e6+5;
int n,m,a[N],b[N],st[N],tp=0,fa[N],fb[N];
ull ans[N];
vector<pii> g[N];

struct tag{ull cx,cy,xy,x,y,c;}tg[N<<2];
struct sgt{
  ull s,xy,x,y;
  sgt operator +(const sgt &t)const{return (sgt){s+t.s,xy+t.xy,x+t.x,y+t.y};}
}tr[N<<2];

#define mid ((l+r)>>1)
#define lson l,mid,p<<1
#define rson mid+1,r,p<<1|1
#define lc p<<1
#define rc p<<1|1

void pu(int p){tr[p]=tr[lc]+tr[rc];}

/*先历史值累计再更新*/
void change(int l,int r,int p,tag t){
  ull &cx=tg[p].cx,&cy=tg[p].cy,&xy=tg[p].xy,&x=tg[p].x,&y=tg[p].y,&c=tg[p].c;
  int len=r-l+1;
  
  if(cx&&cy) c+=t.xy*cx*cy+t.x*cx+t.y*cy+t.c;
  else if(cx) y+=t.y+cx*t.xy,c+=cx*t.x+t.c;
  else if(cy) x+=t.x+cy*t.xy,c+=cy*t.y+t.c;
  else x+=t.x,y+=t.y,xy+=t.xy,c+=t.c;
  if(t.cx) cx=t.cx;
  if(t.cy) cy=t.cy;
  
  ull &s=tr[p].s,&sxy=tr[p].xy,&sx=tr[p].x,&sy=tr[p].y; 
  s+=t.xy*sxy+t.x*sx+t.y*sy+1ull*t.c*len;
  if(t.cx&&t.cy) sxy=t.cx*t.cy*len,sx=t.cx*len,sy=t.cy*len;
  else if(t.cx) sxy=t.cx*sy,sx=t.cx*len;
  else if(t.cy) sxy=t.cy*sx,sy=t.cy*len;
}

void pd(int l,int r,int p){
  if(tg[p].cx||tg[p].cy||tg[p].x||tg[p].xy||tg[p].y||tg[p].c) 
    change(lson,tg[p]),change(rson,tg[p]),tg[p]=(tag){0,0,0,0,0,0};
}

void upd(int l,int r,int p,int x,int y,tag t){
  if(x<=l&&y>=r) return change(l,r,p,t);pd(l,r,p);
  if(x<=mid) upd(lson,x,y,t);
  if(y>mid) upd(rson,x,y,t);pu(p);
}

ull qry(int l,int r,int p,int x,int y){
  if(x<=l&&y>=r) return tr[p].s;pd(l,r,p);
  if(y<=mid) return qry(lson,x,y);
  if(x>mid) return qry(rson,x,y);
  return qry(lson,x,y)+qry(rson,x,y);
}

int main(){
  /*2023.11.30 H_W_Y P8868 [NOIP2022] 比赛 SGT*/
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;cin>>n;
  for(int i=1;i<=n;i++){
  	cin>>a[i];
  	while(tp>0&&a[st[tp]]<a[i]) tp--;
  	fa[i]=st[tp]+1;st[++tp]=i;
  }tp=0;
  for(int i=1;i<=n;i++){
  	cin>>b[i];
  	while(tp>0&&b[st[tp]]<b[i]) tp--;
  	fb[i]=st[tp]+1;st[++tp]=i;
  }
  cin>>m;
  for(int i=1,l,r;i<=m;i++) cin>>l>>r,g[r].pb({l,i});
  for(int r=1;r<=n;r++){
  	upd(1,n,1,fa[r],r,(tag){1ull*a[r],0,0,0,0,0});
  	upd(1,n,1,fb[r],r,(tag){0,1ull*b[r],0,0,0,0});
  	upd(1,n,1,1,r,(tag){0,0,1,0,0,0});
  	for(auto j:g[r]) ans[j.se]=qry(1,n,1,j.fi,r);
  }
  for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
  return 0;
}

Conclusion

  1. c++ 20 之后就不能用 char s;cin>>s+1; 了。(A. [NOIP2022] 种花)
  2. 树形 dp 的状态不只是可以维护子树中的状态,还可以记录 i 到根节点的状态。(G. 石老板告老还乡)

本文作者:H_W_Y

本文链接:https://www.cnblogs.com/H-W-Y/p/noip2022.html

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

posted @   H_W_Y  阅读(42)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起