[模拟赛]2022.07.09

难度:提高组

考场得分:100+100+10+0


[数学]T1 牛牛的方程式

求方程 a x + b y + c z + d ax+by+cz+d ax+by+cz+d 是否有整数解。 − 1 0 18 ≤ a , b , c , d ≤ 1 0 18 -10^{18}\leq a,b,c,d \leq 10^{18} 1018a,b,c,d1018


给的样例中 2 8 8 3已经给了暗示,告诉我们当 a , b , c a,b,c a,b,c 同为偶数是 d d d 不可能为奇数。扩展开来,当 d d d 不能整除 g c d ( a , b , c ) gcd(a,b,c) gcd(a,b,c) 时则为无解,反之有解。

但这里需要特判一下 d = 0 d=0 d=0 时一定有解, a = b = c = 0 a=b=c=0 a=b=c=0 d ≠ 0 d\neq0 d=0 时无解,否则会因为 m o d     0 mod\,\,\,0 mod0 报错。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;++i)
#define ROF(i,j,k) for(int i=j;i>=k;--i)


ll in(){
	ll x=0,f=1;
	char c;
	do{
		c=getchar();
		if(c=='-')
			f=-1;
	}while(c>'9' || c<'0');
	while(c>='0' && c<='9'){
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	return x*f;
}

ll gcd(ll x,ll y){
	return (y==0)?x:gcd(y,x%y);
}

ll t,a,b,c,d; 

int main(){
	t=in();
	while(t--){
		//io;
		a=in();b=in();
		c=in();d=in();
		a=abs(a);b=abs(b);
		c=abs(c);d=abs(d);
		if(d==0){
			printf("YES\n");
			continue;
		}
		if(a==0 && b==0 && c==0){
			printf("NO\n");
			continue;
		}
		if(d%gcd(gcd(a,b),c)==0){
			printf("YES\n");
		}
		else{
			printf("NO\n");
		}
	}
	return 0;
}

[倍增,前缀和]T2 牛牛的猜球游戏

0 − 9 0-9 09 的数列,给定长度为 n n n 的操作序列,每次交换 p , q p,q p,q 两数的位置。然后给出 m m m 个区间 [ l , r ] [l,r] [l,r] 对于初始序列顺次操作,求得到的最终序列。 n , m ≤ 1 0 5 n,m\leq 10^5 n,m105


先给出考场上做法,对于操作进行倍增,记 f [ i ] [ j ] f[i][j] f[i][j] 为从第 j j j 次操作起的后 2 i 2^i 2i 次操作后各个数的位置变化,这里用一个映射的思想建一个结构体存数组,对于其中的 a [ i ] a[i] a[i] 表示原来第 i i i 个位置的数现在在第 a [ i ] a[i] a[i] 个位置,并重载加法运算符。时空都是 O ( n l o g n ) O(nlogn) O(nlogn)

正解做法是 O ( n ) O(n) O(n) 的,思路也十分简洁。还是刚才映射的思想,但是用前缀和进行统计,在求 [ l , r ] [l,r] [l,r] 时先反向从 f [ l ] f[l] f[l] 退回到 f [ 1 ] f[1] f[1] 然后在从 f [ 1 ] f[1] f[1] f [ r ] f[r] f[r] 。非常简单!(程序直接照搬了std)

倍增版

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;++i)
#define ROF(i,j,k) for(int i=j;i>=k;--i)


ll in(){
	ll x=0,f=1;
	char c;
	do{
		c=getchar();
		if(c=='-')
			f=-1;
	}while(c>'9' || c<'0');
	while(c>='0' && c<='9'){
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	return x*f;
}

struct s{
	int a[10];
	void build(){
		for(int i=0;i<=9;++i)
			a[i]=i;
	}
	s operator + (const s &b){
		s c;
		for(int i=0;i<=9;++i){
			c.a[i]=a[b.a[i]];
		}
		return c;
	}
	void print(){
		for(int i=0;i<=8;++i)
			printf("%d ",a[i]);
		printf("%d\n",a[9]);
	}
};

int n,m;
s st[20][100005];

int main(){
	n=in(),m=in();
	FOR(i,1,n){
		int l=in(),r=in();
		st[0][i].build();
		swap(st[0][i].a[l],st[0][i].a[r]);
	}
	FOR(i,1,17){
		FOR(j,1,n){
			st[i][j]=st[i-1][j]+st[i-1][j+(1<<(i-1))];
		}
	}
	while(m--){
		int l=in(),r=in();
		s ans;
		ans.build();
		ROF(i,17,0){
			if(l+(1<<(i))<=r){
				ans=ans+st[i][l];
				l+=(1<<(i));
			}
		}
		ans=ans+st[0][l];
		ans.print();
	}
	return 0;
}
 

前缀和版

#include <bits/stdc++.h>
using namespace std;
const int MAXN=100005;
struct ballList{
    int pos[10];
};
ballList operator - (const ballList A,const ballList B){
    ballList C,temp;
    for(int i=0;i<10;++i){
        temp.pos[B.pos[i]]=i;
    }
    for(int i=0;i<10;++i){
        C.pos[i]=temp.pos[A.pos[i]];
    }
    return C;
}
ostream & operator << (ostream& os,const ballList &ob){
    for(int i=0;i<10;++i){
        if(i)os<<' ';
        os<<ob.pos[i];
    }
    return os;
}
ballList presum[MAXN];
int n,m,u[MAXN],v[MAXN],l,r;
ballList bf(int l,int r){
    ballList ret;
    for(int i=0;i<10;++i){
        ret.pos[i]=i;
    }
    for(int i=l;i<=r;++i){
        swap(ret.pos[u[i]],ret.pos[v[i]]);
    }
    return ret;
}

int main()
{
    ios::sync_with_stdio(false);
    for(int i=0;i<10;++i){
        presum->pos[i]=i;
    }
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>u[i]>>v[i];
        presum[i]=presum[i-1];
        swap(presum[i].pos[u[i]],presum[i].pos[v[i]]);
    }
    for(int i=1;i<=m;++i){
        cin>>l>>r;
        cout<<presum[r]-presum[l-1]<<endl;
    }
    return 0;
}

[主席树]T3 牛牛的凑数游戏

给定数列 S S S ,提问 m m m 次,每次找出用 S l − S r S_l-S_r SlSr 最小的无法凑出的数(每个数只让用一遍)。 n , m ≤ 1 0 5 , a i ≤ 1 0 9 n,m\leq 10^5,a_i\leq 10^9 n,m105,ai109


考场上用枚举大暴力得到 10 p t 10pt 10pt ,然后就再也想不出思路来了。其实是一个比较单一的算法,只需要重复以下过程:

  1. 求出区间内比 i i i 小的所有数字和 s u m [ i ] sum[i] sum[i]
  2. i ≤ s u m [ i ] + 1 i\leq sum[i]+1 isum[i]+1 ,则将 i i i 赋值为 s u m [ i ] + i sum[i]+i sum[i]+i ,反之 s u m [ i ] + 1 sum[i]+1 sum[i]+1 即为所求。

若按照这个思路直接暴力只能拿 20 p t 20pt 20pt O ( m n l o g n ) O(mnlogn) O(mnlogn) ),那么如何优化呢?

可以发现, s u m [ i ] sum[i] sum[i] 增加的过程是一个倍增的过程,整体复杂度不超过 l o g ( 1 0 9 ) log(10^9) log(109) ,那么问题就落在如何快速求出区间内比 i i i 小的数之和了。考虑主席树,主席树本就有求解静态区的功能(可持久化权值线段树),只要对它稍加改造,在每个节点记录子树之和即可。这个过程是 O ( l o g ( 1 0 9 ) ) O(log(10^9)) O(log(109)) ,那么综合起来就是此题答案,时间复杂度 O ( m l o g 2 n ) O(mlog^2n) O(mlog2n)

#include<bits/stdc++.h>
#define ll long long
namespace PresidentTree{
	struct node{
		int ls,rs;
		ll sum;
	}nd[20000005];
	int ncnt,root[100005];
	#define ls(p) (nd[p].ls)
	#define rs(p) (nd[p].rs)
	inline int clone(int p){
		nd[++ncnt]=nd[p];
		return ncnt;
	}
	int add(int p,ll l,ll r,ll k){
		p=clone(p);
		nd[p].sum+=k;
		if(l==r){
			return p;
		}
		int mid=(l+r)>>1;
		if(k<=mid){
			ls(p)=add(ls(p),l,mid,k);
		}
		else{
			rs(p)=add(rs(p),mid+1,r,k);
		}
		return p;
	}
	ll query(int pl,int pr,ll ql,ll qr,ll l,ll r){
		if(ql>r || qr<l)
			return 0;
		if(ql<=l && r<=qr){
			return nd[pr].sum-nd[pl].sum;
		}
		int mid=(l+r)>>1;
		return query(ls(pl),ls(pr),ql,qr,l,mid)+query(rs(pl),rs(pr),ql,qr,mid+1,r);
	}
}
using namespace PresidentTree;

ll n,m;
int main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;++i){
		ll ai;
		scanf("%lld",&ai);
		root[i]=add(root[i-1],1,1000000000ll,ai);
	}
	while(m--){
		ll l,r;
		scanf("%lld %lld",&l,&r);
		ll pl=0,pr=0;
		while(1){
			ll p=query(root[l-1],root[r],pl+1,pr+1,1,1000000000ll);
			if(p==0){
				printf("%lld\n",pr+1);
				break;
			}
			pl=pr+1;
			pr=pr+p;
		}
	} 
	return 0;
}

[?]T4 牛牛的RPG游戏

给定 v a l [ i ] [ j ] , b u f f [ i ] [ j ] val[i][j],buff[i][j] val[i][j],buff[i][j] 分别表示触发 ( i , j ) (i,j) (i,j) 处机关后的得分变化和每走一步的得分变化, b u f f buff buff 可以被刷新,求走到 ( i , j ) (i,j) (i,j) 权值最大。(每一步只能向下,向右)。 n × m ≤ 1 0 5 n\times m\leq 10^5 n×m105 ∣ v a l [ i , j ] ∣ , ∣ b u f f [ i ] [ j ] ∣ ≤ 1 0 4 |val[i,j]|,|buff[i][j]|\leq 10^4 val[i,j],buff[i][j]104


f [ i ] [ j ] = m a x 1 ≤ p ≤ i , 1 ≤ q ≤ j { b u f f [ p ] [ q ] × ( i + j − p − q ) + f [ p ] [ q ] } + v a l [ i ] [ j ] f[i][j]=max_{1\leq p \leq i,1\leq q\leq j}\{buff[p][q]\times(i+j-p-q)+f[p][q]\}+val[i][j] f[i][j]=max1pi,1qj{buff[p][q]×(i+jpq)+f[p][q]}+val[i][j]

时间复杂度 O ( n 2 m 2 ) O(n^2m^2) O(n2m2) ,得 20 p t 20pt 20pt

10 p t 10pt 10pt 数据 b u f f buff buff 为0,可以用 g [ i ] [ j ] g[i][j] g[i][j] 记录从起点至此的矩形范围内最大的 f [ p ] [ q ] f[p][q] f[p][q] ,这样 f [ i ] [ j ] f[i][j] f[i][j] g [ i ] [ j ] g[i][j] g[i][j] 可以 O ( 1 ) O(1) O(1) 转移。

暂时无法理解满分做法,存疑。

30 p t 30pt 30pt 做法

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;++i)
#define ROF(i,j,k) for(int i=j;i>=k;--i)

ll in(){
	ll x=0,f=1;
	char c;
	do{
		c=getchar();
		if(c=='-')
			f=-1;
	}while(c>'9' || c<'0');
	while(c>='0' && c<='9'){
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	return x*f;
}

int n,m;

inline int p(int a,int b){
	return a*(m+1)+b;
}


int val[200005],buff[200005];
int f[200005],g[200005],flag;
int main(){
	memset(f,-0x7f,sizeof(f));
	n=in(),m=in();
	FOR(i,1,n){
		FOR(j,1,m){
			buff[p(i,j)]=in();
			if(buff[p(i,j)]!=0)
				flag=1;
		}
	}
	FOR(i,1,n){
		FOR(j,1,m){
			val[p(i,j)]=in();
		}
	}
	f[p(1,1)]=0;
	if(flag==0){
		FOR(i,1,n){
			FOR(j,1,m){
				f[p(i,j)]=max(f[p(i,j)],max(g[p(i,j-1)],g[p(i-1,j)])+val[p(i,j)]);
				g[p(i,j)]=max(f[p(i,j)],max(g[p(i,j-1)],g[p(i-1,j)]));
			}
		}
		printf("%d\n",f[p(n,m)]);
		return 0;
	}
	FOR(i,1,n){
		FOR(j,1,m){
			FOR(k,1,i){
				FOR(l,1,j){
					if(i==k && j==l)
						continue;
					f[p(i,j)]=max(f[p(k,l)]+val[p(i,j)]+buff[p(k,l)]*(i+j-k-l),f[p(i,j)]);
				}
			}
			
		}
	}
	printf("%d",f[p(n,m)]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值