noip模拟11[math·biology·english]

solution

怎么说这次考试考得一点也不好,别问为什么,就是没考好

而且听说,我们上一届师兄考这个题,一模一样的,有人\(AK\)

而我只有\(130pts\),是我菜了

上来先干的第一题,然后干了半天,打出来一个背包,然后 \(TLE\)---\(70pts\)

这是我第一次栽在这么简单的题上,而且栽的这么惨

然后去搞第二题,这个我的思路在下面说吧

然后第三题只留下了1小时左右,就只打了个暴力就溜了

有个教训:不要在考试的时候放弃已经想出来一半的思路!!!

还要一步一步的去找到正解,不要想一步登天,因为根本不现实

T1 math

这题说简单也简单,说难吧,你要是想不到那个定理,一切都白搭

先说说我的暴力思路吧:

对于每一个\(a[i]\),我们都能找到不超过\(k\)个数,使得它为\(a[i]\)的倍数\(\mod k\)得到的

这样我们就有了\(n×k\)个数,然后,你们还记不记得砝码称重那道题,

你再二进制拆分一下,然后跑个小背包,然后,就拿到了\(70pts\)

其实这个思路还可以优化,但是我现在找不到优化的办法,因为这k个数应该是可以一步拆成二进制

code·my



#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=5e5+5;
const int M=1e6+5;
int n,len,k;
int a[N];
bool vis[M],pd[M];
int ji[M],cnt;
int dp[M],ans;
signed main(){
	//freopen("a.ans","w",stdout);
	scanf("%d%d",&n,&k);
	for(re i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i]%=k;
	}
	sort(a+1,a+n+1);
	len=unique(a+1,a+n+1)-a-1;
	for(re i=1;i<=len;i++){
		int x;
		int tmp=a[i];
		for(re j=0;j>=0;j++){
			if(pd[tmp]){
				x=j;
				break;
			}
			if(!vis[tmp])ji[++cnt]=tmp;
			pd[tmp]=vis[tmp]=true;
			tmp=tmp*2%k;
			//cout<<j<<endl;
		}
		tmp=a[i];
		for(re j=0;j<=x;j++)pd[tmp]=false,tmp=tmp*2%k;
	}
	memset(dp,0x8f,sizeof(dp));
	dp[0]=0;
	for(re i=1;i<=cnt;i++){
		for(re j=k-1;j>=ji[i];j--){
			dp[j]=max(dp[j],dp[j-ji[i]]+ji[i]);
		}
	}
	for(re i=0;i<k;i++)
		if(dp[i]>=0)ans++;
	printf("%d\n",ans);
	for(re i=0;i<k;i++)
		if(dp[i]>=0)printf("%d ",i);
	//cout<<endl;
}

·

所以我们还是说说正解吧:

首先这里有一个结论,\(a*x+b*y=z\)这个式子有解的充分必要条件是\(GCD(a,b)|z\)

那么你就有思路了呗,剩下的就很容易想出来了,

对啦,就是,把那n个数全部求一个gcd,然后一直跳就可以啦,跳到重复为止

好哟,这个这个题就这么简单的写完了,那么就去看代码吧

code

#include bits/stdc++.h
using namespace std;
#define re register int
const int N=5e5+5;
int n,k;
int a[N],gcd;
int vis[N*2],ans;
int ji[N*2];
int GCD(int x,int y){
	if(y==0)return x;
	else return GCD(y,x%y);
}
signed main(){
	scanf("%d%d",&n,&k);
	for(re i=1;i<=n;i++){
		scanf("%d",&a[i]);
		if(i!=1){
			gcd=GCD(a[i],gcd);
		}
		else gcd=a[i];
	}
	gcd%=k;
	for(re i=0;i>=0&&!vis[gcd*i%k];i++){
		ans++;vis[gcd*i%k]=1;
		ji[ans]=gcd*i%k;
	}
	sort(ji+1,ji+ans+1);
	printf("%d\n",ans);
	for(re i=1;i<=ans;i++){
		printf("%d ",ji[i]);
	}
}

所以这个题的教训就是,我要好好背背结论啦,要不然啥也不会啦

T2 biology

哼,我觉得整场考试就这个题一点技术含量没有(虽然吧,我只有40pts,嘿嘿)

这个题我一眼就想到暴力dp啦,还小小的优化了一下,虽然这个优化,只能优化一个\(\frac{1}{4}\)

那也是优化啦。。。。然而并没有多拿多少分

·

所以暴力dp的思路就是,就是先把所有的点存到一个结构体里,然后呐,按照先a,后b的双关键字排序

但是,我突然发现这样做好像麻烦了诶,然后我就把a离散化咯,然后就用了个vector,这样的话更好访问每一个点

这样做完之后,就可以去转移啦,再自己手动模一摸,然后发现,肯定是走的节点越多,得到的价值越大

所以我们就只能一层一层的转移哦,然后这个复杂度就比\(n^2m^2\)小好多哦

这就是我的暴力算法啦,虽然没啥技术含量但是还是很有水准的

想完暴力之后,我一瞬间就想到了数状数组,然后发现没时间了啊啊,然后就跑了

考完之后,回来发现正解是。。。。

code·my

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=2e3+5;
int n,m;
int a[N][N],b[N][N];
ll dp1[2][N],ans;
ll dp[N*N];
struct node{
	ll a,b,x,y;
	bool operator < (node o)const{
		if(o.a!=a)return o.a>a;
		return o.b>b;
	}
}pot[N*N];
int cnt;
signed main(){
	int flag=0;
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(a[i][j]!=i)flag=1;
		}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++)
			scanf("%d",&b[i][j]);
	if(flag==0){
		int now=0,fro=1;
		memset(dp1,0x8f,sizeof(dp1));
		for(re i=1;i<=m;i++){
			dp1[now][i]=b[1][i];
		}
		for(re i=2;i<=n;i++){
			swap(now,fro);
			for(re j=1;j<=m;j++){
				for(re k=1;k<=m;k++){
					dp1[now][j]=max(dp1[now][j],dp1[fro][k]+abs(j-k)+1);
				}
				dp1[now][j]+=b[i][j];
			}
		}
		for(re i=1;i<=m;i++)ans=max(ans,dp1[now][i]);
		printf("%lld",ans);
		return 0;
	}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			pot[++cnt].x=i;
			pot[cnt].y=j;
			pot[cnt].a=a[i][j];
			pot[cnt].b=b[i][j];
		}
	//cout<<"case 2"<<endl;
	memset(dp,0x8f,sizeof(dp));
	sort(pot+1,pot+cnt+1);
	int fro=0,now=0,s1,t1,s2,t2;
	for(re i=1;i<=cnt;i++){
		//cout<<pot[i].a<<" "<<pot[i].b<<endl;
		if(pot[i].a!=now){
			fro=now;t1=i-1;s1=s2;
			s2=i;now=pot[i].a;
		}
		if(fro==0&&now!=0)dp[i]=pot[i].b;
		else if(fro!=0){
			for(re j=s1;j<=t1;j++){
				int tx=abs(pot[i].x-pot[j].x);
				int ty=abs(pot[i].y-pot[j].y);
				dp[i]=max(dp[i],dp[j]+tx+ty+pot[i].b);
			}
			ans=max(dp[i],ans);
		}
	}
	printf("%lld",ans);
}

·

的确有数状数组的做法,只能拿到80pts因为超时

是这样的,我们直接吧绝对值拆开啊,因为这样才能有单调性啊,

    1:x1-x2+y1-y2
    2:x1-x2+y2-y1
    3:x2-x1+y1-y2
    4:x2-x1+y2-y1

就是这四种情况分别对应在此时这个点的四个方位

我们根据a来建立数状数组,每次查询最大值更新

复杂度是\(O(nmlognlogm)\)的,,这样就有了80分

code·segtree



#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=2e3+5;
const int M=1e6+5;
int n,m;
int a[N][N],b[N][N];
ll dp[N][N],ans;
bool vis[M];
int lsh[M],lh,fsh[M];
vector< pair<int,int> > pot[M];
struct seg_tree{
	ll tr[N][N];
	ll lb(int x){return x&(-x);}
	void ins(int x,int y,ll v){
		for(re i=x;i<=n;i+=lb(i)){
			for(re j=y;j<=m;j+=lb(j)){
				tr[i][j]=max(tr[i][j],v);
			}
		}
	}
	ll query(int x,int y){
		ll ret=0;
		for(re i=x;i;i-=lb(i)){
			for(re j=y;j;j-=lb(j)){
				ret=max(ret,tr[i][j]);
			}
		}
		return ret;
	}
}t1,t2,t3,t4;
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(!vis[a[i][j]]&&a[i][j]){
				lsh[++lh]=a[i][j];
				vis[a[i][j]]=true;
			}
		}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++)
			scanf("%d",&b[i][j]);
	sort(lsh+1,lsh+lh+1);
	for(re i=1;i<=lh;i++)
		fsh[lsh[i]]=i;
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			if(!a[i][j])continue;
			a[i][j]=fsh[a[i][j]];
			pot[a[i][j]].push_back(make_pair(i,j));
		}
	for(re i=0;i<pot[1].size();i++){
		int x=pot[1][i].first;
		int y=pot[1][i].second;
		t1.ins(x,y,b[x][y]-x-y);
		t2.ins(x,m+1-y,b[x][y]-x+y);
		t3.ins(n+1-x,y,b[x][y]+x-y);
		t4.ins(n+1-x,m+1-y,b[x][y]+x+y);
	}
	for(re i=2;i<=lh;i++){
		for(re j=0;j<pot[i].size();j++){
			int x=pot[i][j].first;
			int y=pot[i][j].second;
			dp[x][y]=max(dp[x][y],t1.query(x,y)+x+y+b[x][y]);
			dp[x][y]=max(dp[x][y],t2.query(x,m+1-y)+x-y+b[x][y]);
			dp[x][y]=max(dp[x][y],t3.query(n+1-x,y)-x+y+b[x][y]);
			dp[x][y]=max(dp[x][y],t4.query(n+1-x,m+1-y)-x-y+b[x][y]);
		}
		for(re j=0;j<pot[i].size();j++){
			int x=pot[i][j].first;
			int y=pot[i][j].second;
			t1.ins(x,y,dp[x][y]-x-y);
			t2.ins(x,m+1-y,dp[x][y]-x+y);
			t3.ins(n+1-x,y,dp[x][y]+x-y);
			t4.ins(n+1-x,m+1-y,dp[x][y]+x+y);
		}
	}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++)
			ans=max(ans,dp[i][j]);
	printf("%lld",ans);
}

·

下面就是正解啦:

后来你发现,这个数状数组是完全没有意义的

为什么呢?因为你曼哈顿距离,就是这两个点的坐标进行加减运算的最大值,

所以如果你找到了一个最大值,那么他一定是合法的

也就是说在这个题的条件下,每一个合法方案都是最优方案

那么我们只需要一个小变量来计算就好了,代码实现极其简单,按照上面数状数组的改改就好啦

code·correct

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=2e3+5;
const int M=1e6+5;
int n,m;
int a[N][N],b[N][N];
ll dp[N][N],ans;
bool vis[M];
int lsh[M],lh,fsh[M];
vector< pair<int,int> > pot[M];
struct seg_tree{
	ll tr[N][N];
	ll lb(int x){return x&(-x);}
	void ins(int x,int y,ll v){
		for(re i=x;i<=n;i+=lb(i)){
			for(re j=y;j<=m;j+=lb(j)){
				tr[i][j]=max(tr[i][j],v);
			}
		}
	}
	ll query(int x,int y){
		ll ret=0;
		for(re i=x;i;i-=lb(i)){
			for(re j=y;j;j-=lb(j)){
				ret=max(ret,tr[i][j]);
			}
		}
		return ret;
	}
}t1,t2,t3,t4;
ll op1,op2,op3,op4;
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(!vis[a[i][j]]&&a[i][j]){
				lsh[++lh]=a[i][j];
				vis[a[i][j]]=true;
			}
		}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++)
			scanf("%d",&b[i][j]);
	sort(lsh+1,lsh+lh+1);
	for(re i=1;i<=lh;i++)
		fsh[lsh[i]]=i;
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++){
			if(!a[i][j])continue;
			a[i][j]=fsh[a[i][j]];
			pot[a[i][j]].push_back(make_pair(i,j));
		}
	op1=op2=op3=op4=0x8fffffffffffffff;
	for(re i=0;i<pot[1].size();i++){
		ll x=pot[1][i].first;
		ll y=pot[1][i].second;
		op1=max(op1,b[x][y]-x-y);
		op2=max(op2,b[x][y]-x+y);
		op3=max(op3,b[x][y]+x-y);
		op4=max(op4,b[x][y]+x+y);
	}
	for(re i=2;i<=lh;i++){
		for(re j=0;j<pot[i].size();j++){
			ll x=pot[i][j].first;
			ll y=pot[i][j].second;
			dp[x][y]=max(dp[x][y],op1+x+y+b[x][y]);
			dp[x][y]=max(dp[x][y],op2+x-y+b[x][y]);
			dp[x][y]=max(dp[x][y],op3-x+y+b[x][y]);
			dp[x][y]=max(dp[x][y],op4-x-y+b[x][y]);
		}
		for(re j=0;j<pot[i].size();j++){
			ll x=pot[i][j].first;
			ll y=pot[i][j].second;
			op1=max(op1,dp[x][y]-x-y);
			op2=max(op2,dp[x][y]-x+y);
			op3=max(op3,dp[x][y]+x-y);
			op4=max(op4,dp[x][y]+x+y);
		}
	}
	for(re i=1;i<=n;i++)
		for(re j=1;j<=m;j++)
			ans=max(ans,dp[i][j]);
	printf("%lld",ans);
}

我好像还看见有的题解上把曼哈顿距离转化为了切莫雪夫距离

我觉得没啥用,都是这四种情况相互转移啦,就这样咯

T3 english

这个题难死啦,难死啦

我觉得暴力这个还是非常好打的 哈哈哈

code-my
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const ll mod=1e9+7;
const int N=1e5+5;
int n,opt;
ll a[N];
ll st[N][22],lg[N];
ll ans1,ans2;
void st_pre(){
	lg[0]=-1;
	for(re i=1;i<=n;i++)lg[i]=lg[i>>1]+1,st[i][0]=a[i];
	for(re j=1;j<=20;j++){
		for(re i=1;i+(1<<j)-1<=n;i++){
			st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	}
}
ll st_get(int l,int r){
	int tmp=lg[r-l+1];
	return max(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
}
signed main(){
	scanf("%d%d",&n,&opt);
	for(re i=1;i<=n;i++)scanf("%lld",&a[i]);
	st_pre();
	for(re i=1;i<=n;i++){
		for(re j=i;j<=n;j++){
			ll xo=a[i]^a[j];
			ll maxn=st_get(i,j);
			ans1=(ans1+xo*maxn)%mod;
			ans2=(ans2+(xo>maxn?1:0)*maxn)%mod;
		}
	}
	switch(opt){
		case 1:
			printf("%lld",ans1);
			break;
		case 2:
			printf("%lld",ans2);
			break;
		case 3:
			printf("%lld\n%lld",ans1,ans2);
			break;
	}
}

·

一看到这破题面,我立刻就想到:单调栈求这个点可以控制的区间的左右边界

priority_stack
for(re i=1;i<=n;i++){
		while(a[i]>=a[sta[tot]]&&tot)r[sta[tot]]=i-1,tot--;
		l[i]=sta[tot]+1;
		sta[++tot]=i;
	}
	while(tot)r[sta[tot]]=n,tot--;

然后我的思路到这里就停止了

后来看到题解,发现这是一颗可持久化trie树

和一般的题一样,把要加起来的数,拆成每一位去算,一般这样做会减少很多的时间复杂度

然后我们就去单个分析没一个点

ans1:直接在这个节点的两边,0×1,1×0,然后就有了

ans2:哪边少枚举哪边,然后另外一边,用trie树查找,就有了

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=2e5+25;
const int mod=1e9+7;
int n,opt;
int a[N];
int l[N],r[N],sta[N],tot;
int sum[N][25];
ll ans1,ans2;
int rt[N];
struct trie{
	int siz[N*30];
	int son[N*30][2];
	int seg;
	void ins(int prt,int &rt,int x,int dep){
		rt=++seg;siz[rt]=siz[prt];
		if(dep<0){
			siz[rt]++;
			return ;
		}
		int tmp=(x>>dep)&1;
		son[rt][tmp^1]=son[prt][tmp^1];
		ins(son[prt][tmp],son[rt][tmp],x,dep-1);
		siz[rt]=siz[son[rt][0]]+siz[son[rt][1]];
		return ;
	}
	int query(int x,int y,int pos){
		int u=rt[pos],ret=0;
		for(re i=20;i>=0;i--){
			int a=(x>>i)&1;
			int b=(y>>i)&1;
			if(b==0){
				ret+=siz[son[u][a^1]];
				u=son[u][a];
			}
			else u=son[u][a^1];
			if(!u)break;
		}
		return ret;
	}
}t;
void sol(int x,int l,int r){
	if(x-l<r-x){
		for(re i=l;i<=x;i++){
			for(re j=20;j>=0;j--){
				if((a[i]>>j)&1)ans1=(ans1+1ll*(1<<j)*(r-x+1-sum[r][j]+sum[x-1][j])%mod*a[x]%mod)%mod;
				else ans1=(ans1+1ll*(1<<j)*(sum[r][j]-sum[x-1][j])%mod*a[x]%mod)%mod;
			}
			ans2=(ans2+1ll*(t.query(a[i],a[x],r)-t.query(a[i],a[x],x-1)+mod)*a[x]%mod)%mod;
		}
	}
	else{
		for(re i=x;i<=r;i++){
			for(re j=20;j>=0;j--){
				if((a[i]>>j)&1)ans1=(ans1+1ll*(1<<j)*(x-l+1-sum[x][j]+sum[l-1][j])%mod*a[x]%mod)%mod;
				else ans1=(ans1+1ll*(1<<j)*(sum[x][j]-sum[l-1][j])%mod*a[x]%mod)%mod;
			}
			ans2=(ans2+1ll*(t.query(a[i],a[x],x)-t.query(a[i],a[x],l-1)+mod)*a[x]%mod)%mod;
		}
	}
}
signed main(){
	scanf("%d%d",&n,&opt);
	for(re i=1;i<=n;i++)scanf("%d",&a[i]),t.ins(rt[i-1],rt[i],a[i],20);
	for(re i=1;i<=n;i++){
		while(a[i]>=a[sta[tot]]&&tot)r[sta[tot]]=i-1,tot--;
		l[i]=sta[tot]+1;
		sta[++tot]=i;
	}
	while(tot)r[sta[tot]]=n,tot--;
	for(re i=1;i<=n;i++){
		for(re j=0;j<=20;j++){
			if(1&(a[i]>>j))sum[i][j]=sum[i-1][j]+1;
			else sum[i][j]=sum[i-1][j];
		}
	}
	for(re i=1;i<=n;i++){
		sol(i,l[i],r[i]);
		//cout<<ans1<<" "<<ans2<<endl;
	}
	if(opt&1)printf("%lld\n",ans1);
	if(opt&2)printf("%lld",ans2);
}

完结快乐

下次一定A题

posted @ 2021-07-11 18:52  fengwu2005  阅读(93)  评论(0)    收藏  举报