2024.2.20 初三集训模拟测试2
和 DZ,lxyt 一起和初三考的一个模拟赛,不知道为啥让考,而且又是考试前头一天晚上告诉我们的(关键还是晚上下了课才告诉我们的)。
考的也还行,只不过 \(T2\) 没有用心去想,没打。
考试排名(三个难兄难弟)
T1 小P的2048
虽然你很强,但赢的人只会是我。
本身 \(T1\) 是迷之阶乘,DZ 还说之前做过,结果刚开考不到 \(5\) 分钟喵喵换题了。不过还好,换成了一个小模拟。
按照题意模拟即可。
交给我吧!这次绝不搞砸。
#include <bits/stdc++.h>
#define N 10
#define int long long
using namespace std;
int n,m,a[N][N],b[N][N],num,ans;
queue < int > q;
void Input(){
int x,y,v,xx,yy,vv;scanf("%lld%lld",&n,&m);
scanf("%lld%lld%lld%lld%lld%lld",&x,&y,&v,&xx,&yy,&vv);
a[x][y] = v,a[xx][yy] = vv;
}
void work_UP(int K,int V){
memset(b,0,sizeof(b));
for(int j = 1;j <= n;j ++){
for(int i = 1;i <= n;i ++) if(a[i][j] != 0) q.push(a[i][j]);
int tot = 0;while(!q.empty()){
int x = q.front();q.pop();
if(q.empty() or x != q.front()) b[++tot][j] = x;
else b[++tot][j] = x * 2,q.pop(),num += x * 2;
}
}
}
void work_DOWN(int K,int V){
memset(b,0,sizeof(b));
for(int j = 1;j <= n;j ++){
for(int i = n;i >= 1;i --) if(a[i][j] != 0) q.push(a[i][j]);
int tot = n + 1;while(!q.empty()){
int x = q.front();q.pop();
if(q.empty() or x != q.front()) b[--tot][j] = x;
else b[--tot][j] = x * 2,q.pop(),num += x * 2;
}
}
}
void work_LEFT(int K,int V){
memset(b,0,sizeof(b));
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= n;j ++) if(a[i][j] != 0) q.push(a[i][j]);
int tot = 0;while(!q.empty()){
int x = q.front();q.pop();
if(q.empty() or x != q.front()) b[i][++tot] = x;
else b[i][++tot] = x * 2,q.pop(),num += x * 2;
}
}
}
void work_RIGHT(int K,int V){
memset(b,0,sizeof(b));
for(int i = 1;i <= n;i ++){
for(int j = n;j >= 1;j --) if(a[i][j] != 0) q.push(a[i][j]);
int tot = n + 1;while(!q.empty()){
int x = q.front();q.pop();
if(q.empty() or x != q.front()) b[i][--tot] = x;
else b[i][--tot] = x * 2,q.pop(),num += x * 2;
}
}
}
void work(){
int opt,K,V,tot = 0;while(m --){
scanf("%lld%lld%lld",&opt,&K,&V);
if(opt == 0) work_UP(K,V);
if(opt == 1) work_DOWN(K,V);
if(opt == 2) work_LEFT(K,V);
if(opt == 3) work_RIGHT(K,V);
int r = 0,z = 0;
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= n;j ++){
if(a[i][j] != b[i][j]) z = 1;
a[i][j] = b[i][j];
if(a[i][j] == 0) r ++;
}
}
if(z == 0) break;tot ++;
r = 1 + K % r;int g = 0;
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= n;j ++){
if(a[i][j] == 0) g ++;
if(g == r) {a[i][j] = V;break;}
}
if(g == r) break;
}
}
printf("%lld\n%lld\n",tot,num);
}
signed main(){
Input();
work();
return 0;
}
T2 子集
努力完成任务才能有钱赚,有钱赚了才能吃饱饭。
特判 \(n=k\) 的情况。
我们令 \(m=\dfrac{n}{k}\)。
如果 \(m\) 是偶数,随便搞,提供一种构造方案:
1 | 5 | 9 | 16 | 20 | 24 |
---|---|---|---|---|---|
2 | 6 | 10 | 15 | 19 | 23 |
3 | 7 | 11 | 14 | 18 | 22 |
4 | 8 | 12 | 13 | 17 | 21 |
如果 \(m\) 是奇数:
如果 \(n\) 是偶数,输出 No
。把 \(n\) 个数分成 \(k\) 份,那么每份是 \(\dfrac{n\times (n+1)}{2\times k}=\dfrac{n+1}{2}\times m\),因此当 \(m\) 是奇数、\(n\) 是偶数时,不合法。
\(n\) 为奇数时其实也有很多构造方案,提供一种:
我们先构造出前三列,然后后面那些根据偶数的构造方式进行构造。
第一列我们还是从 \(1\) 开始顺次向下排,第二列构造的时候,我们找到一种方法让每一行前两列的和加起来后排序后相邻两项的差为 \(1\),这里提供一种构造方案:
1 | 12 |
---|---|
2 | 13 |
3 | 14 |
4 | 8 |
5 | 9 |
6 | 10 |
7 | 11 |
应该也能看懂怎么构造。然后第三列就让前三列相等这么构造就行了。后面的按照偶数的方法构造。
收手吧,你会受伤的。
#include <bits/stdc++.h>
using namespace std;
int T,n,k;
unordered_map < int , unordered_map < int , int > > a;
int main(){
scanf("%d",&T);while(T --){
scanf("%d%d",&n,&k);
if(n == k and k != 1) {puts("No");continue;}
int m = n / k;a.clear();
if(m % 2 == 0){
int tot = 0;puts("Yes");
for(int j = 1;j <= m / 2;j ++){
for(int i = 1;i <= k;i ++){
a[i][j] = ++tot;
}
}
for(int j = m / 2 + 1;j <= m;j ++){
for(int i = k;i >= 1;i --){
a[i][j] = ++tot;
}
}
for(int i = 1;i <= k;i ++){
for(int j = 1;j <= m;j ++){
printf("%d ",a[i][j]);
}puts("");
}
}
else{
if(n % 2 == 0) {puts("No");continue;}
int tot = 0;puts("Yes");
for(int i = 1;i <= k;i ++) a[i][1] = ++tot;
int z1 = k * 3,z2 = k * 3 - 1;
for(int i = k / 2 + 1;i <= k;i ++) a[i][2] = ++tot,a[i][3] = z1,z1 -= 2;
for(int i = 1;i <= k / 2;i ++) a[i][2] = ++tot,a[i][3] = z2,z2 -= 2;
tot += k;
for(int j = 4;j <= (m - 3) / 2 + 3;j ++){
for(int i = 1;i <= k;i ++){
a[i][j] = ++tot;
}
}
for(int j = (m - 3) / 2 + 4;j <= m;j ++){
for(int i = k;i >= 1;i --){
a[i][j] = ++tot;
}
}
for(int i = 1;i <= k;i ++){
for(int j = 1;j <= m;j ++){
printf("%d ",a[i][j]);
}puts("");
}
}
}
return 0;
}
T3 混凝土粉末
我的记忆有限,不想给仇恨留有空间。
将询问离线下来,在序列下标上执行扫描线,并开一个以询问编号为下表的树状数组。
当扫描到一个修改的左端点的时候,就在树状数组这个修改的编号的位置加上其对应的 \(h_i\),扫到右端点(注意应该在 \(r+1\) 的位置)的时候在这个修改的编号的位置减去其对应的 \(h_i\)。
当扫描到一个询问的时候,在树状数组上二分即可(好像也算是使用了倍增的思想)。注意我们找的是这个树状数组在当前询问编号的位置前的所有位置中的 \(h\) 中第一个大于等于 \(y\) 的位置。可以参考代码。时间复杂度 \(O(n\log n)\)。
疤痕不是勋章,它们只会在下雨天发热发烫。
#include <bits/stdc++.h>
#define N 1000006
#define int long long
using namespace std;
vector < pair < int , int > > a[N],b[N];
int n,q,ans[N];bool vis[N];
struct Set_Tree{
int c[N];
void add(int x,int y) {for(int i = x;i <= q;i += i & (-i)) c[i] += y;}
int query(int x,int y){
int pos = 0,sum = 0,np;
for(int i = 20;i >= 0;i --){
if((np = pos + (1 << i)) <= x and sum + c[np] < y){
sum += c[np],pos = np;
}
}
return pos == x ? 0 : pos + 1;
}
}Set;
signed main(){
scanf("%lld%lld",&n,&q);
int opt,l,r,h,x,y;for(int i = 1;i <= q;i ++){
scanf("%lld",&opt);
if(opt == 1){
scanf("%lld%lld%lld",&l,&r,&h);
a[l].push_back({i,h});
a[r + 1].push_back({i,-h});
}
if(opt == 2){
scanf("%lld%lld",&x,&y);
b[x].push_back({i,y});
vis[i] = 1;
}
}
for(int i = 1;i <= n;i ++){
for(auto j : a[i]) Set.add(j.first,j.second);
for(auto j : b[i]) ans[j.first] = Set.query(j.first,j.second);
}
for(int i = 1;i <= q;i ++) if(vis[i]) printf("%lld\n",ans[i]);
return 0;
}
T4 排水系统
我的名字,大概是叫亚连吧。
期望 等于 每个取值 乘 得到该取值的概率的 积 的总和。
分数取模:如果模数 \(P\) 为质数,那么 $\dfrac{a}{b}\bmod P=a\times b^{P-2}\bmod P $
我们使用样例二进行解释。
下文中,我们定义点权表示这个点可以流出的污水的值,初始状态表示这个图每个点最开始的时候的点权(也就是 \(1\) 到 \(m\) 为 \(1\),其他为 \(0\))。
先使用拓扑排序,我们先把所有管道都不堵塞的时候每个点的点权求出来,我们将这求出来的点权定义为 \(num_{1,…,n}\)。
对于下图,边权代表这个管道老化的概率。
然后,我们思考一下:对于一个管道 \(u\to v\) 堵塞,相当于 \(u\) 的其他出点的点权会增加,\(v\) 的点权会减小。但是如果我们一个一个地处理 \(u\) 的其他出点,肯定会寄,所以我们就直接让 \(u\) 的点权增加,\(v\) 的点权减小。(不过注意这里 \(v\) 的减小量和前面的有所不同)
那么我们思路就有了,我们把这些值的期望都加到初始状态的图上,就可以得到每一个点初始状态点权的期望值,然后再在这个图上在跑一边就可以求得每个最终排水口排出的期望值。
OK,现在整体思路有了,然后我们思考一下这些值怎么计算。
我们定义 \(du_i\) 表示 \(i\) 点的出度。
那么 \(u\) 点增加的值就是 \(\Delta u=\dfrac{num_u}{du_u-1}\times du_u-num_u=\dfrac{num_u}{du_u-1}\),\(v\) 点减小的值就是 \(\Delta v=num_v+\dfrac{\Delta u}{du_u}=\dfrac{num_u}{du_u-1}\),然后再把他们两个乘上删去 \(u\to v\) 这条边的概率,就是他们的期望,然后再把他们加到初始状态的图的即可。最后全部处理完毕后在新处理出来的图上跑一边,即可求得结果。
继续模拟一下样例:
然后我们再把算出来的这些 \(\Delta\) 全部加入到初始状态的图中。(注意初始状态中只有点 \(1\) 的点权是 \(1\),其他全部是 \(0\))
然后我们再在这张图上重新跑一边即可。
可以参考着代码进行理解。
双剑,出鞘!需要我解决什么?什么都可以。
#include <bits/stdc++.h>
#define N 500005
#define int long long
#define MOD 998244353
using namespace std;
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int n,m,r,k,num[N],ans[N],JD,sum,C[N],R1[N],R2[N],R3[N];
queue < int > q;
struct Edge{int next,to,dis;}edge[N];
int head[N],cnt;
void add(int from,int to,int dis){
edge[++cnt] = (Edge){head[from],to,dis};
head[from] = cnt;
}
void bfs3(){
while(!q.empty()){
int x = q.front();q.pop();
int res = num[x] * ksm(C[x] % MOD,MOD - 2) % MOD;
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;num[y] += res,num[y] %= MOD;
R3[y] --;if(!R3[y]) q.push(y);
}
}
}
void bfs2(){
while(!q.empty()){
int x = q.front();q.pop();
int res = ans[x] * ksm(C[x] % MOD,MOD - 2) % MOD;
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;ans[y] += res,ans[y] %= MOD;
R2[y] --;if(!R2[y]) q.push(y);
}
}
}
void bfs1(){
while(!q.empty()){
int x = q.front();q.pop();
for(int i = head[x];i;i = edge[i].next){
int y = edge[i].to;
int o = num[x] * ksm((C[x] - 1) % MOD,MOD - 2) % MOD;
ans[x] += (C[x] * o % MOD - num[x] + MOD) % MOD * edge[i].dis % MOD * JD % MOD,ans[x] %= MOD;
ans[y] += (MOD - o) % MOD * edge[i].dis % MOD * JD % MOD,ans[y] %= MOD;
R1[y] --;if(!R1[y]) q.push(y);
}
}
}
signed main(){
scanf("%lld%lld%lld%lld",&n,&m,&r,&k);
for(int i = 1,u,v,w;i <= k;i ++){
scanf("%lld%lld%lld",&u,&v,&w);add(u,v,w);
sum += w,sum %= MOD;R1[v] ++,R2[v] ++,R3[v] ++,C[u] ++;
}
for(int i = 1;i <= m;i ++) num[i] = 1,q.push(i);bfs3();
JD = ksm(sum,MOD - 2);
for(int i = 1;i <= m;i ++) ans[i] = 1,q.push(i);bfs1();
for(int i = 1;i <= m;i ++) q.push(i);bfs2();
for(int i = n - r + 1;i <= n;i ++) printf("%lld ",ans[i]);
return 0;
}