2024初三集训模拟测试2

T0 谜之阶乘

题意:给定一个数 \(x\)\(x\)\(\dfrac{a!}{b!}\),求 \(a\)\(b\)。(多测,\(1\le x\le 10^{18}\)
阶乘增长很快,\(a\)\(b\) 的范围很小,所以直接暴力枚举即可。

T1 小P的2048

点击查看题目

image

根据题意模拟即可,根据方向决定遍历顺序比较方便。
点击查看代码
#include<bits/stdc++.h>
#define int long long
inline int read(){
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
const int N=10;
int last[N][N],now[N][N],ans,sum,n,m;
bool vis[N][N];
inline void up(){
	for(int i=2;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(now[i][j]){
				int x=i,y=j;
				while(x>1){
					if(now[x-1][y]){
						if(now[x-1][y]==now[x][y]){
							if(!vis[x-1][y]){
								vis[x-1][y]=1;
								now[x-1][y]*=2;
								sum+=now[x-1][y];
								now[x][y]=0;
							}
						}
						break;
					}else{
						now[x-1][y]=now[x][y];
						now[x][y]=0;
						x--;
					}
				}
			}
		}
	}
}
inline void down(){
	for(int i=n-1;i;--i){
		for(int j=1;j<=n;++j){
			if(now[i][j]){
				int x=i,y=j;
				while(x<n){
					if(now[x+1][y]){
						if(now[x+1][y]==now[x][y]){
							if(!vis[x+1][y]){
								vis[x+1][y]=1;
								now[x+1][y]*=2;
								sum+=now[x+1][y];
								now[x][y]=0;
							}
						}
						break;
					}else{
						now[x+1][y]=now[x][y];
						now[x][y]=0;
						x++;
					}
				}
			}
		}
	}
}
inline void left(){
	for(int j=2;j<=n;++j){
		for(int i=1;i<=n;++i){
			if(now[i][j]){
				int x=i,y=j;
				while(y>1){
					if(now[x][y-1]){
						if(now[x][y-1]==now[x][y]){
							if(!vis[x][y-1]){
								vis[x][y-1]=1;
								now[x][y-1]*=2;
								sum+=now[x][y-1];
								now[x][y]=0;
							}
						}
						break;
					}else{
						now[x][y-1]=now[x][y];
						now[x][y]=0;
						y--;
					}
				}
			}
		}
	}
}
inline void right(){
	for(int j=n-1;j;--j){
		for(int i=1;i<=n;++i){
			if(now[i][j]){
				int x=i,y=j;
				while(y<n){
					if(now[x][y+1]){
						if(now[x][y+1]==now[x][y]){
							if(!vis[x][y+1]){
								vis[x][y+1]=1;
								now[x][y+1]*=2;
								sum+=now[x][y+1];
								now[x][y]=0;
							}
						}
						break;
					}else{
						now[x][y+1]=now[x][y];
						now[x][y]=0;
						y++;
					}
				}
			}
		}
	}
}
inline void add(int k,int v){
	int tot=0,zc=0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(!now[i][j])tot++;
	tot=1+(k%tot);
	for(int i=1;i<=n;++i){
		bool pd=0;
		for(int j=1;j<=n;++j){
			if(!now[i][j])zc++;
			if(zc==tot){now[i][j]=v;pd=1;break;}
		}
		if(pd)break;
	}
}
inline void check(){
	ans++;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(last[i][j]!=now[i][j])return;
	ans--;
	std::cout<<ans<<'\n'<<sum<<'\n';exit(0);
}
signed main(){
	// freopen("in.in","r",stdin),freopen("out.out","w",stdout);
	std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);
	n=read(),m=read();
	int x1=read(),y1=read(),v1=read(),x2=read(),y2=read(),v2=read();
	last[x1][y1]=v1,last[x2][y2]=v2;now[x1][y1]=v1,now[x2][y2]=v2;
	for(int i=1;i<=m;++i){
		memset(vis,0,sizeof(vis));
		int d=read(),k=read(),v=read();
		if(d==0)up();
		if(d==1)down();
		if(d==2)left();
		if(d==3)right();
		check();
		add(k,v);
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				last[i][j]=now[i][j];
	}
	std::cout<<ans<<'\n'<<sum<<'\n';
}

T2 子集

点击查看题目

image

My Algorithm
\(n\) 个数平均分为 \(k\) 组,每组有 \(len=\dfrac{n}{k}\) 个数,将 \(k\) 个数划分为一组。只需要在这 \(len\) 组数中每组选一个即可构造出一组答案。
关于 \(k\)\(len\),有如下情况:

  • \(k\) 为奇数,\(len\) 为奇数。
  • \(k\) 为奇数,\(len\) 为偶数。
  • \(k\) 为偶数,\(len\) 为偶数。

至于为什么没有 \(k\) 为偶数,\(len\) 为奇数。这种情况,证明如下:
\(k\) 为偶数,\(len\) 为奇数。时,显然有 \(sum=\dfrac{n\cdot (n+1)}{2}\) 为奇数,此时 \(k\nmid sum\),无解。
偶数随便构造,下面介绍奇数的构造。
奇数的构造比较困难在于 \(len\) 为奇数,如果按照偶数的方式来构造的话,就会剩下一组,并且所有组都要用到剩下这一组中间的数,可以先构造出公差为 \(1\) 的一些数,然后直接再按照偶数构造的方式构造 \(n-2\) 列,这样他们的差也是 \(1\),这么说不好理解,附图如下。
n=15 k=3
image
发现红色区域的构造和偶数如出一辙,但由于 \(len\) 为奇数,所以他们的差为 \(1\),所以继续构造出 \(3\) 组数,每组有 \(2\) 个数,且他们的和差值为 \(1\)
前面容易处理,考虑后面,经过观察奇数和奇数,偶数和偶数,发现他们之间都相差 \(2\),所以构造出为在两段中的差值都为 \(1\)
发现奇数对应前一段,偶数对应后一段,如 n=25 k=5 这样的情况。
image
相信你已经看出规律了。代码如下。

点击查看代码
#include<bits/stdc++.h>
#define int long long
inline int read(){
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
signed main(){
	// freopen("in.in","r",stdin),freopen("out.out","w",stdout);
	std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);
	int T=read();
	while(T--){
		int n=read(),k=read();
		int len=n/k,s=n*(n+1)/2;
		if(s%k||(k==n&&n!=1)){std::cout<<"No\n";continue;}
		std::cout<<"Yes\n";
		if(k==1){
			for(int i=1;i<=n;++i)std::cout<<i<<' ';
			std::cout<<'\n';continue;
		}
		if(len&1){
			int qian=len/2;
			for(int i=1;i<=k;++i){
				int sum=0;
				for(int j=i;j<=qian*k;j+=k){
					std::cout<<j<<' ';sum+=j;
				}
				int w=len-2;
				for(int j=w*k-i+1;j>qian*k;j-=k){
					std::cout<<j<<' ';sum+=j;
				}
				sum=s/k-sum;
				if(i&1){
					int zc=n-(i+1)/2+1;
					std::cout<<sum-zc<<' '<<zc;
				}else{
					int zc=len/2*2*k-(i/2)+1;
					std::cout<<zc<<' '<<sum-zc;
				}
				std::cout<<'\n';
			}
		}else{
			for(int i=1;i<=k;++i){
				for(int j=i;j<=n/2;j+=k)std::cout<<j<<' ';
				for(int j=n-i+1;j>n/2;j-=k)std::cout<<j<<' ';
				std::cout<<'\n';
			}
		}
	}	
}

T3 混凝土粉末

点击查看题目

image

询问本质是:作若干次区间加,并询问 $x$ 位置上的值最早 $h_x\ge y$ 的时间。

无脑做法为:主席树加二分,空间会炸,常数会炸,和优秀暴力一个分。

点击查看代码
#include<bits/stdc++.h>
#define int long long
inline int read(){
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
const int N=1e6+10;
int n,m,cnt,root[N],a[N],now;
struct Tree{int ls,rs,h,tag;}t[N<<4];
inline void modify(int last,int p,int x,int y,int d,int l,int r){
	t[p]=t[last];
	if(x<=l&&r<=y){t[p].tag=t[p].tag+d;return;}
	int mid=l+r>>1;
	if(x<=mid)modify(t[last].ls,t[p].ls=++cnt,x,y,d,l,mid);
	if(y>mid)modify(t[last].rs,t[p].rs=++cnt,x,y,d,mid+1,r);
}
inline int query(int p,int x,int l,int r){
	if(l==r){return t[p].h+t[p].tag;}
	int mid=l+r>>1;
	if(x<=mid)return query(t[p].ls,x,l,mid)+t[p].tag;
	else return query(t[p].rs,x,mid+1,r)+t[p].tag;
}
inline int work(int x,int y,int r){
	if(query(root[r],x,1,n)<y)return 0;
	int ans=0,l=1;
	while(l<=r){
		int mid=l+r>>1;
		if(query(root[mid],x,1,n)>=y)r=mid-1,ans=mid;
		else l=mid+1;
	}
	return ans;
}
signed main(){
	// freopen("in.in","r",stdin),freopen("out.out","w",stdout);
	std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);
	n=read(),m=read();
	for(int i=1;i<=m;++i){
		root[i]=root[i-1];
		int opt=read();
		if(opt==1){
			int l=read(),r=read(),h=read();
			modify(root[i-1],root[i]=++cnt,l,r,h,1,n);
		}else{
			int x=read(),y=read();
			std::cout<<work(x,y,i)<<'\n';
		}
	}
}

正解做法为:把操作离线下来,扫描 \(1\)\(n\) 的位置,用下标为操作时间的树状数组维护每个位置的所有操作时间的信息。
具体如下:扫描到一个位置 \(i\),如果他是左端点,在树状数组上区间加 \(d\),是右端点就区间减 \(d\),消除这次操作的影响。此时树状数组维护的就是位置 \(i\) 在所有时刻的信息。如果是询问的话就在树状数组上倍增,找到第一个 \(h_i\ge y\) 的时间。

点击查看代码
#include<bits/stdc++.h>
#define int long long
inline int read(){
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
const int N=1e6+10;
int n,q;
std::vector<std::pair<int,int>> st[N],qu[N];
struct Tree{
	int c[N];
	inline void add(int x,int d){for(int i=x;i<=q;i+=i&-i)c[i]+=d;}
	inline int query(int x,int y){
		int l=0,t=0;
		for(int i=std::__lg(x);i>=0;--i)
			if((l|1<<i)<=x&&t+c[(l|1<<i)|l]<y)
				t+=c[l|1<<i],l+=1<<i;
		return l==x?0:l+1;
	}
}t;
int __[N];
bool vis[N];
signed main(){
	// freopen("in.in","r",stdin),freopen("out.out","w",stdout);
	std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);
	n=read(),q=read();
	for(int i=1;i<=q;++i){
		int opt=read();
		if(opt==1){
			int l=read(),r=read(),h=read();
			st[l].push_back({i,h});
			st[r+1].push_back({i,-h});
		}else{
			int x=read(),y=read();
			qu[x].push_back({i,y});
			vis[i]=true;
		}
	}
	for(int i=1;i<=n;++i){
		for(auto it:st[i])t.add(it.first,it.second);
		for(auto it:qu[i])__[it.first]=t.query(it.first,it.second);
	}
	for(int i=1;i<=q;++i)if(vis[i])std::cout<<__[i]<<'\n';
}

大多数人的做法为:正解,但是没有在树状数组上二分,而是二分套树状数组。

这是白简的代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#define int long long

using namespace std;

const int N = 2005000;

int n,q;
int opt[N],res[N];

vector< pair<int,int> > g[N],h[N];

class BIT{
	private:
		int bit[N];
		
		int lowbit(int x) {
			return x & (-x);
		}
	public:
		void Add(int x,int y,int val) {
			for(int i = x;i <= y; i += lowbit(i))
				bit[i] += val;
		}
		
		int Query(int x) {
			int ans = 0;
			
			for(int i = x;i; i -= lowbit(i))
				ans += bit[i];
				
			return ans;
		}
}tree;

signed main() {
	cin >> n >> q;
	for(int i = 1,l,r,x,y;i <= q; i++) {
		cin >> opt[i];
		if(opt[i] == 1) {
			cin >> l >> r >> x;
			g[l].push_back(make_pair(i,x));
			g[r + 1].push_back(make_pair(i,-x));
		}
		else {
			cin >> x >> y;
			h[x].push_back(make_pair(i,y));
		}
	}
	
	for(int i = 1;i <= n; i++) {
		for(int j = 0;j < g[i].size(); j++)
			tree.Add(g[i][j].first,q,g[i][j].second);
		
		for(int j = 0;j < h[i].size(); j++) {
			int l = 1,r = h[i][j].first,mid;
			
			while(l < r) {
				mid = (l + r) >> 1;
				if(tree.Query(mid) >= h[i][j].second)
					res[h[i][j].first] = r = mid;
				else
					l = mid + 1;
			}
		}
	}
	
	for(int i = 1;i <= q; i++)
		if(opt[i] == 2) 
			cout << res[i] << endl;
	return 0;
} 

不知道为啥这个双 log 有人写的跑的比正解快,技不如人,膜拜~~

这里有一个赛时nishishui123的高分暴力,和主席树同分
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+100;
int n,q,p,num;
ll x,y;
struct node{
    int l,r,id;
    ll h;
}name[N];
ll height;
int main()
{
    scanf("%d%d",&n,&q);
    for(int t=1;t<=q;t++){
        scanf("%d",&p);
        if(p==1){
            ++num;
            scanf("%d%d%lld",&name[num].l,&name[num].r,&name[num].h);
            name[num].id=t;
        }
        else{
            scanf("%lld%lld",&x,&y);
            height=0;
            bool flag=0;
            for(int i=1;i<=num;i++){
                if(name[i].l<=x&&x<=name[i].r){
                    height+=name[i].h;
                    if(height>=y){
                        flag=1;
                        printf("%d\n",name[i].id);
                        break;
                    }
                }
            }
            if(flag==0)puts("0");
        }
    }
}

T4 排水系统

点击查看题目

image

暴力是枚举每条边,可以考虑删去每条边的影响。
统计每个点的出度 \(out_i\),入度 \(in_i\)
直接拓扑即可,首先求出没有堵塞时所有点的初始状态 \(ans_i\)
然后进行第二次拓扑,以此把每条边当做堵塞边,当这条边 \((u,v)\) 堵塞之后,考虑它带了的影响。显然,除了它连的子点之外,其他子点都会受影响,这样的子点有很多,不好处理,所以考虑怎样处理这条边相连的两个点。我们想要其他点改变,所以必须要改变父节点 \(u\) 的信息,此时其他点会均分到堵塞点 \(v\)\(ans_{u->v}=\dfrac{ans_u}{out_u}\),推式子也可以,但是这样说更好理解。\(v\) 节点要平均分为 \(out_u-1\) 份给其他点,这样需要在 \(u\) 节点上加上 \(out_u\) 份,而跑的时候 \(v\) 会再次分到,所以先减去原来的 \(out_u-1\) 份,再减去分给的 \(1\) 份,共 \(out_u\) 份,所以 \(val=\dfrac{ans_{u->v}}{out_u-1}\cdot out_u=\dfrac{ans_u}{out_u-1}\),直接给 \(u\) 加上这个贡献,给 \(v\) 减去这个贡献即可,式子非常好推。
处理完后再跑一遍拓扑算答案即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
inline int read(){
    char ch=getchar();int x=0,f=1;
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
    return x*f;
}
const int N=2e5+10,mod=998244353;
int n,m,r,k,head[N],in1[N],out[N],in2[N],in3[N],tot,ans[N],_ans[N],sum;
struct Qu{
    int l=0,r=-1,s[N];
    inline bool empty(){return r<l;}
    inline int front(){return s[l];}
    inline void push(int a){s[++r]=a;}
    inline void pop(){l++;}
    inline void clear(){l=0,r=-1;}
}q;
inline int qpow(int a,int b){
    int ans=1;
    for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;
    return ans;
}
inline int ny(int x){return qpow(x,mod-2);}
struct edge{int v,next,w;}e[N<<3];
inline void add(int u,int v,int w){e[++tot]={v,head[u],w};head[u]=tot;}
inline void work1(){
    q.clear();
    for(int i=1;i<=m;++i)q.push(i),ans[i]=1;
    while(!q.empty()){
        int x=q.front();q.pop();
        int val=ans[x]*ny(out[x])%mod;
        for(int i=head[x];i;i=e[i].next){
            ans[e[i].v]=(ans[e[i].v]+val)%mod;
            if(!--in1[e[i].v])q.push(e[i].v);
        }
    }
}
inline void work2(){
    q.clear();
    for(int i=1;i<=m;++i)q.push(i),_ans[i]=1;
    while(!q.empty()){
        int x=q.front();q.pop();
        int val=ans[x]*ny(out[x]-1)%mod;
        for(int i=head[x];i;i=e[i].next){
            int o=val*e[i].w%mod*ny(sum)%mod;
            _ans[x]=(_ans[x]+o)%mod;
            _ans[e[i].v]=((_ans[e[i].v]-o)%mod+mod)%mod;
            if(!--in2[e[i].v])q.push(e[i].v);
        }
    }
}
inline void work3(){
    q.clear();
    for(int i=1;i<=m;++i)q.push(i);
    while(!q.empty()){
        int x=q.front();q.pop();
        int val=_ans[x]*ny(out[x])%mod;
        for(int i=head[x];i;i=e[i].next){
            _ans[e[i].v]=(_ans[e[i].v]+val)%mod;
            if(!--in3[e[i].v])q.push(e[i].v);
        }
    }
}
signed main(){
    //freopen("in.in","r",stdin),freopen("out.out","w",stdout);
    std::ios::sync_with_stdio(false);
    std::cin.tie(0),std::cout.tie(0);
    n=read(),m=read(),r=read(),k=read();
    for(int i=1;i<=k;++i){
        int u=read(),v=read(),w=read();add(u,v,w);
        in1[v]++,in2[v]++,in3[v]++;out[u]++;sum=(sum+w)%mod;
    }
    work1();work2();work3();
    for(int i=n-r+1;i<=n;++i)std::cout<<_ans[i]<<' ';
}
posted @ 2024-02-21 07:49  Ishar-zdl  阅读(28)  评论(0编辑  收藏  举报