当前位置: 首页 > news >正文

NOIP-2022 题解

T1

一眼是计数类的题目,那就要思考怎么计数了
这道题目还是很简单的
类似于动态规划,只要找到转移的方法就行了,从哪里可以做出来
首先 , 先考虑 C 因为 F 是 C 下边随便加一个点 , 所以只要求出 C 就求出了 F 。 首先, 先考虑 C 因为 F 是 C 下边随便加一个点, 所以只要求出 C 就求出了 F 。 首先,先考虑C因为FC下边随便加一个点,所以只要求出C就求出了F
下边来考虑如何求 C 。 下边来考虑如何求 C 。 下边来考虑如何求C
注意到 , 他并没有要求上下行一样 , 唯一的要求是 C 的两个横要隔一行 , 这就是问题的突破点 , 这题很明显的计数 . 注意到, 他并没有要求上下行一样, 唯一的要求是 C 的两个横要隔一行, 这就是问题的突破点, 这题很明显的计数. 注意到,他并没有要求上下行一样,唯一的要求是C的两个横要隔一行,这就是问题的突破点,这题很明显的计数.
计数用到什么 ? 乘法原理 , 加法原理。 计数用到什么? 乘法原理, 加法原理。 计数用到什么?乘法原理,加法原理。
假设上边的有 a 个合法的横 , 那考虑这一行每一个合法的横(这里说的不同是长度不同)给答案的贡献是什么 ? 假设上边的有 a 个合法的横, 那考虑这一行每一个合法的横(这里说的不同是长度不同)给答案的贡献是什么? 假设上边的有a个合法的横,那考虑这一行每一个合法的横(这里说的不同是长度不同)给答案的贡献是什么?
是不是每一个贡献 a , 那这一行有 b 个不同的合法的横 , 那么答案就增加了 a × b , 是不是每一个贡献 a , 那这一行有 b 个不同的合法的横, 那么答案就增加了 a \times b , 是不是每一个贡献a,那这一行有b个不同的合法的横,那么答案就增加了a×b,
那每一行都这么处理 , 然后处理完一样就加上上一行的合法的方案数(因为他要求两个横之间至少隔一行)。 那每一行都这么处理, 然后处理完一样就加上上一行的合法的方案数(因为他要求两个横之间至少隔一行)。 那每一行都这么处理,然后处理完一样就加上上一行的合法的方案数(因为他要求两个横之间至少隔一行)。
当遇到土坑的时候就把记录数组清零即可 . 当遇到土坑的时候就把记录数组清零即可. 当遇到土坑的时候就把记录数组清零即可.
C 解决了 , 那就是 F 了、想要求出 F , 只要求出这一列上有多少合法的 C 就行了(因为 F 是由 C 下边加上一个坚构成的) , C 解决了, 那就是 F 了、想要求出 F , 只要求出这一列上有多少合法的 C 就行了(因为 F 是由 C 下边加上一个坚构成的), C解决了,那就是F了、想要求出F,只要求出这一列上有多少合法的C就行了(因为F是由C下边加上一个坚构成的),
所所以我们只要复制过来记录 C 的公式然后把他存在另一个数组里到时候每找到一个能种花的地方 F 的答案加上这个数组就好了。 所所以我们只要复制过来记录 C 的公式然后把他存在另一个数组里到时候每找到一个能种花的地方 F 的答案加上这个数组就好了。 所所以我们只要复制过来记录C的公式然后把他存在另一个数组里到时候每找到一个能种花的地方F的答案加上这个数组就好了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MOD=998244353;
int read(){int x=0,f=1;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;
}
const int N=1010;
int ans1,ans2,c,F;
int n,m,id,T;
int f[N][N];
int js,j2;
char mp[N][N];
signed main() {T=read(),id=read();while(T--) {memset(f,0,sizeof f);n=read(),m=read();c=read(),F=read();for(int i=1; i<=n; i++) {for(int j=1; j<=m; j++) {cin>>mp[i][j];	}}for(int i=1; i<=n; i++){for(int j=m-1; j>=1; j--) {if(mp[i][j]=='1') f[i][j]=-1;else if(mp[i][j+1]=='0') f[i][j]=f[i][j+1]+1;}}for(int j=1; j<m; j++) {js=j2=0;for(int i=1; i<=n; i++) {if(f[i][j]==-1) {j2=js=0;continue ;}ans1=ans1%MOD+(1ll*f[i][j]*(js%MOD))%MOD;ans2=(ans2%MOD+j2%MOD)%MOD;j2=(j2+(1ll*f[i][j]*(js%MOD))%MOD)%MOD;js=js+max((int)0,f[i-1][j]);}}cout<<(c*ans1)%MOD<<' '<<(F*ans2)%MOD<<endl;ans1=ans2=0;}return 0;
}

T2

这道题还是有一点难度的
优先跳过
回头再看这个题,感觉很好理解,一开始没有关注k的范围,现在一看,发现k只有两种方案,所以只需要对k进行讨论就行了
k = 2 ( n − 1 ) k=2(n-1) k=2(n1) 时,这个时候就可以在每个栈中放入两个元素,然后留出来一个特殊栈,特殊栈就是要消元素用的。

这样对于某个栈, 来一个属于该栈的卡牌时, 若和底部卡牌相同就利用特殊栈进行 2 操作, 否则就直接放上去。这样可以保证每时每刻, 前 n-1 个栈的卡牌个数不超过 2 。

k = 2 n − 1 k=2 n-1 k=2n1 时, 此时会出现一种情况: n − 1 n-1 n1 个栈都包含 2 2 2 个卡牌了, 此时又来最后一种卡牌。设该卡牌为 w w w , 找到 n − 1 n-1 n1 个放满的栈中底部卡牌之后最先出现的栈 x x x, x x x 栈底设为 u u u , 栈顶设为 v v v , 分下面三种情况讨论:

  • w w w 的下一次出现时间早于 u u u , 那么可以 w w w 直接放到特殊栈(因为特殊栈的作用是消除底部, 在 u u u 下次出现之前都不会用到特殊栈)
  • u u u 下次出现时间早于 v v v , 那么可以把 w w w 放到 x x x 顶部(虽然此时栈内会有 3 3 3 个卡牌导致中间的 v v v 无法消除,但是 u u u 会早于 v v v 离开)
  • 剩下的情况意味着下次出现的时间是 v < u < w v<u<w v<u<w ,那么可以把 w w w 放到特殊栈,然后规定接下来的特殊栈改为 x x x (很明显在 x x x 清空之前,都不会存在 2 2 2 操作,所以没影响)

优化成 O ( m ) O(m) O(m) 要注意几个点:

  • 需要一个数组维护每个卡牌当前所在栈的编号
  • 需要一个队列来分配每个新来的卡牌属于哪个普通栈的,初始每个普通栈能提供两个空位,卡牌出栈会归还空位
  • 查找下一个最先出现底部元素的栈,可以暴力往后找,因为下一次再出现放满栈的局面一定在底部元素出栈后(若是第一种情况 w w w 先出,就循环到下个 w w w 结束)。这样所有暴力找的部分不会有交叉, 总循环次数不超过 m m m
#include<bits/stdc++.h>
using namespace std;
int rd(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;
}
const int N=2e6+10;
int T,n,m,k,a[N],cnt,spe,belong[N];
struct node{int op,x,y;}ans[N<<2];
vector<deque<int> > s(310);
queue<int> q;
void opera1(int x,int t){ans[++cnt]={1,x};if(s[x].size()>0&&s[x].back()==t){s[x].pop_back();if(x!=spe&&s[x].size()<2) q.push(x);belong[t]=0;}else{s[x].push_back(t);belong[t]=x;}
}
void opera2(int x,int y){ans[++cnt]={2,x,y};belong[s[x].front()]=0;s[x].pop_front(),s[y].pop_front();if(s[x].size()<2) q.push(x);
}
void init(){while(!q.empty()) q.pop();cnt=0;s.clear();s.resize(310);for(int i=0;i<=k;i++) belong[i]=0;
}
int main(){T=rd;while(T--){init();n=rd,m=rd,k=rd;for(int i=1;i<=m;i++) a[i]=rd;for(int i=1;i<=n-1;i++) q.push(i),q.push(i);spe=n;for(int i=1;i<=m;i++){int x=belong[a[i]];if(x){if(s[x].size()==1) opera1(x,a[i]);else if(s[x].back()==a[i]) opera1(x,a[i]);else opera1(spe,a[i]),opera2(x,spe);}else if(q.size()>0){x=q.front();q.pop();opera1(x,a[i]);}else{for(int j=i+1;;j++){if(a[j]==a[i]) break;if(s[belong[a[j]]].front()==a[j]){x=belong[a[j]];break;}}if(!x) opera1(spe,a[i]);else{int u=s[x].front(),v=s[x].back();for(int j=i+1;;j++){if(a[j]==u){opera1(x,a[i]);break;}else if(a[j]==v){opera1(spe,a[i]),q.push(spe),spe=x;break;}}} }}printf("%d\n",cnt);for(int i=1;i<=cnt;i++){if(ans[i].op==1) printf("%d %d\n",ans[i].op,ans[i].x);else printf("%d %d %d\n",ans[i].op,ans[i].x,ans[i].y);}}return 0;
}

T3

这道题之前做过,所以回忆出来还是比较简单的
就是在本地不知道为什们过不了大样例
A 国可以随意地看守或不看守不是割边的边。因此想到 边双缩点 后树形 DP
V u V_{u} Vu 表示双连通分量 u u u 中的点数, E u E_{u} Eu 表示双连通分量 u u u 中的边数, 若有 n n n 个双连通分量, 则问题转化为:

给定一棵无根树, 每个结点有 2 E u 2^{E_{u}} 2Eu 种不建造军营的方案和 ( 2 V u + E u − 2 E u ) \left(2^{V_{u}+E_{u}}-2^{E_{u}}\right) (2Vu+Eu2Eu) 种建造军营的方案。求共有多少种建造军营的方案 (不能不建)。

这里假定 1 1 1 号结点为树根。
f ( u , 0 / 1 ) f(u, 0 / 1) f(u,0/1) 表示以 u 为根的子树中没有/有军营的方案数。
发现每种状态所涵盖的情况过多, 根本不好转移。
这时, 有两种思路:

  • 增加状态数量。
  • 对状态增添限制。

我选择的是后者。
f ( u , 0 / 1 ) f(u, 0 / 1) f(u,0/1) 表示以 u u u 为根的子树中没有/有军营的方案数, 若有军营, 则所有的军营必须通过已经派兵看守的边与 u u u 连通。

在想转移之前, 为了防止做无用功, 最好先想想该如何统计答案。
对于每个结点 u u u , 我们强制 u u u 子树外的所有点都不建军营, 同时强制不选 f a u → u f a_{u} \rightarrow u fauu 的边, 再累加方案数, 即可保证不重不漏

#include<bits/stdc++.h>
using namespace std;#define int long longint read(){int x=0,f=1;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();return x*f;
}
const int N=1e6+10;
int head[N],tot=1;
struct node{int to,nxt;
}e[N*2];
void add(int a,int b){tot++;e[tot].to=b;e[tot].nxt=head[a];head[a]=tot;
}
int n,m;
bool ban[N*4];
int vis[N],cnt[N],f[N][10];
void dfs(int x,int fa){
//	puts("dfs1");vis[x]=1;for(int i=head[x];i;i=e[i].nxt){int y=e[i].to;if(y==fa||ban[i]) continue;if(vis[y]) {ban[i]=ban[i^1]=1;cnt[x]+=1,cnt[y]-=1;continue;}dfs(y,x);}
}
void dfs2(int x,int fa){
//	puts("dfs2");for(int i=head[x];i;i=e[i].nxt){int y=e[i].to;if(y==fa||ban[i]) continue;dfs2(y,x),cnt[x]+=cnt[y];}
}
const int MOD=1e9+7;
void work(int x,int fa){
//	puts("work");f[x][0]=f[x][1]=1;f[x][2]=0;for(int i=head[x];i;i=e[i].nxt){int y=e[i].to;if(y==fa||ban[i]) continue;work(y,x);int t1=0,t2=0,t0=0;if(cnt[y]){t0=2*f[x][0]*f[y][0]%MOD;t1=(2*f[x][0]*f[y][1]%MOD+2*f[x][1]*f[y][0]%MOD+2*f[x][1]*f[y][1]%MOD)%MOD;t2=(2*f[x][0]*f[y][2]%MOD+2*f[x][2]*f[y][0]%MOD)%MOD;}else{t0=2*f[x][0]*f[y][0]%MOD;t1=(f[x][0]*f[y][1]%MOD+2*f[x][1]*f[y][0]%MOD+f[x][1]*f[y][1]%MOD)%MOD;t2=(2*f[x][0]*f[y][2]%MOD+2*f[x][2]*f[y][0]%MOD+f[x][0]*f[y][1]%MOD)%MOD;}f[x][0]=t0,f[x][1]=t1,f[x][2]=t2;}
}
signed main(){
//	freopen("barrack4.in","r",stdin);
//	freopen("1.out","w",stdout);n=read(),m=read();for(int i=1;i<=m;i++){int a=read(),b=read();add(a,b),add(b,a);}
//	puts("popopopopopopopopop");dfs(1,0),dfs2(1,0);work(1,0);
//	cout<<"______________________________";int ans=(f[1][1]+f[1][2])%MOD;for(int i=1;i<=m-n+1;i++){ans=(ans<<1)%MOD;}printf("%lld",ans);return 0;
} 

T4

看同学们写了线段树,但是写挂了
好像要学新知识了
这道题能力有限,只能写部分分了,会了矩阵再说
然后这就是暴力:
考虑离线做, 对所有询问按照 r r r 从小到大一个个来。假设当前考虑到 r r r , 记 x i = max ⁡ j = i r a j , y i = max ⁡ j = i r b j , f i = ∑ j = i r x j y j x_{i}=\max _{j=i}^{r} a_{j}, y_{i}= \max _{j=i}^{r} b_{j}, f_{i}=\sum_{j=i}^{r} x_{j} y_{j} xi=maxj=iraj,yi=maxj=irbj,fi=j=irxjyj , 那么对于询问 ( l , r ) (l, r) (l,r) , 答案就是 ∑ i = l r f i \sum_{i=l}^{r} f_{i} i=lrfi 。这样时间复杂度变为 O ( n 2 + n q ) O\left(n^{2}+n q\right) O(n2+nq) , 可以拿到 20 分的高分, 参考代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ULL unsigned long long 
struct Node {int l, id;
};
const int N=1e6+10;
vector<Node> d[N];
ULL a[N], b[N];
ULL f[N], x[N], y[N];
ULL ans[N];
int main() {int n, q;scanf("%*d%d", &n);for (int i = 1; i <= n; i++) {scanf("%llu", &a[i]);}for (int i = 1; i <= n; i++) {scanf("%llu", &b[i]);}scanf("%d", &q);for (int i = 1; i <= q; i++) {int l, r;scanf("%d%d", &l, &r);d[r].push_back({l, i});}for (int r = 1; r <= n; r++) {for (int i = 1; i <= r; i++) {x[i] = max(x[i], a[r]);y[i] = max(y[i], b[r]);f[i] += x[i] * y[i];}for (auto [l, id] : d[r]) {for (int i = l; i <= r; i++)ans[id] += f[i];}}for (int i = 1; i <= q; i++) printf("%llu\n", ans[i]);return 0;
}

正解还不会,头考NOIP如果补题的话再改

原文地址:https://blog.csdn.net/zhangshuangjiang/article/details/143318137
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/61141.html

相关文章:

  • 人工智能在单细胞测序和空间转录组学中的最新研究进展|顶刊速递·24-10-28
  • 【专用名词的离线语音识别在2024年底的解决方法调查-会议签到的补充】
  • 成品气楼参考图集有哪些?盘点5本实用图集,你都知道哪几本
  • MAC电脑的ifconfig输出
  • 【linux开发-驱动】-RS232/485相关
  • 基于Python的PostgreSQL数据库操作示例(二)
  • Vue3 学习笔记(十一)Vue生命周期
  • linux命令小总结
  • H5底部输入框点击弹起来的时候被软键盘遮挡bug
  • Windows 修改用户名
  • yt-dlp 和 ffmpeg 下载和处理视频的基本命令
  • Zookeeper 和 Eureka 做注册中心有什么区别?
  • 开源智能语音转写系统:助力高效会议记录,精确还原访谈内容
  • 将机器人六轴坐标转为4*4矩阵(Opencv/C++)
  • QTestLib框架
  • SAP-成本要素
  • MoonNet网络库文档
  • svg 初识+了解 + 应用 + 动画
  • 【算法题】树状数组
  • <项目代码>YOLOv8 猫狗识别<目标检测>