难度:提高组
考场得分: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} −1018≤a,b,c,d≤1018
给的样例中 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 0−9 的数列,给定长度为 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,m≤105 。
先给出考场上做法,对于操作进行倍增,记 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 Sl−Sr 最小的无法凑出的数(每个数只让用一遍)。 n , m ≤ 1 0 5 , a i ≤ 1 0 9 n,m\leq 10^5,a_i\leq 10^9 n,m≤105,ai≤109 。
考场上用枚举大暴力得到 10 p t 10pt 10pt ,然后就再也想不出思路来了。其实是一个比较单一的算法,只需要重复以下过程:
- 求出区间内比 i i i 小的所有数字和 s u m [ i ] sum[i] sum[i]
- 若 i ≤ s u m [ i ] + 1 i\leq sum[i]+1 i≤sum[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×m≤105 , ∣ 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]=max1≤p≤i,1≤q≤j{buff[p][q]×(i+j−p−q)+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;
}