noip模拟5

依旧是没拿多少分的一次考试
懒得贴排名了,因为我太菜

考试操作

T1一看,40分白送,果断收下,十分钟带走,然后想怎么优化,觉得应该跟线段树有关系,似乎就没有然后了
然后T2,啊这好像是DP,应该是计数,想起自己dp弱者的身份之后,果断弃掉,20分的暴力状压也没有。。。
T3有20分显然暴力,想到利用前缀和处理有40分,部分分带走,想更深入分析一下然鹅无用
T4好像是图论?tarjan缩点?tarjan是啥?我学过吗?再次continue...你就是个废
回来T1,慢慢想到了思路,建树开打,然而到考试结束也没调完,嘎
40+0+40+0=80 rank12

T1:string

暴力做法不解释,剩下做法都是线段树,只不过常数有差别(还挺大),本人考场想了个建26棵线段树的做法,跟正解差不多,不过中间有点奇怪,常数不小,在线段树函数里写循环,声明数组这事我竟然全干了。。。

#include <bits/stdc++.h>
using namespace std;
char s[100005];int mem[30];
struct tree{
	int l,r;
	int lazy;
	int c[30];
}a[550000];//开到55万 
void build(int id,int l,int r)
{
	a[id].l=l;a[id].r=r;
	memset(a[id].c,0,sizeof(a[id].c));
	if(l==r)
	{
		a[id].c[s[l]-'a'+1]++;
		return ;
	}
	int mid=(l+r)/2;
	build(id*2,l,mid);build(id*2+1,mid+1,r);
	for(int i=1;i<=26;i++)
	a[id].c[i]=a[id*2].c[i]+a[id*2+1].c[i]; 
}
void luo(int id)
{
	if(!a[id].lazy)return;
	int p1[30];memset(p1,0,sizeof(p1));
	int p2[30];memset(p2,0,sizeof(p2));
	int mid=(a[id].l+a[id].r)/2;
	int l1=mid-a[id].l+1;int l2=a[id].r-mid;
	if(a[id].lazy==1)
	{
		int i=1,pp=0;
        for(;i<=26;i++)
        {
    	  if(pp+a[id].c[i]>l1)break;
    	  p1[i]=a[id].c[i];pp+=a[id].c[i];
	    }
	    p1[i]=l1-pp;p2[i]=a[id].c[i]-p1[i];i++;
	    for(;i<=26;i++)p2[i]=a[id].c[i]; 
	    for(int i=1;i<=26;i++)
	    {
		  a[id*2].c[i]=p1[i];a[id*2+1].c[i]=p2[i];
		  a[id*2].lazy=1;a[id*2+1].lazy=1;
	    }
	}
    if(a[id].lazy==2)
	{
		int i=26,pp=0;
        for(;i>=1;i--)
        {
    	  if(pp+a[id].c[i]>l1)break;
    	  p1[i]=a[id].c[i];pp+=a[id].c[i];
	    }
	    p1[i]=l1-pp;p2[i]=a[id].c[i]-p1[i];i--;
	    for(;i>=1;i--)p2[i]=a[id].c[i]; 
	    for(int i=1;i<=26;i++)
	    {
		  a[id*2].c[i]=p1[i];a[id*2+1].c[i]=p2[i];
		  a[id*2].lazy=2;a[id*2+1].lazy=2;
	    }
	}
	a[id].lazy=0;
}
int get(int id,int l,int r,int x)
{
    if(a[id].l==l&&a[id].r==r)return a[id].c[x];
	luo(id);
    int mid=(a[id].l+a[id].r)/2;
    if(mid>=r)return get(id*2,l,r,x);
    else if(mid<l)return get(id*2+1,l,r,x);
    else return get(id*2,l,mid,x)+get(id*2+1,mid+1,r,x);
}
void pai(int id,int l,int r,int sb[],int x)
{
	luo(id);
    if(a[id].l==l&&a[id].r==r)
    {
    	if(x){for(int i=1;i<=26;i++)a[id].c[i]=sb[i];a[id].lazy=1};
    	else{for(int i=1;i<=26;i++)a[id].c[i]=sb[i];a[id].lazy=2;}
        return ;
	}
	int mid=(a[id].l+a[id].r)/2;
	if(mid>=r)pai(id*2,l,r,sb,x);
	else if(mid<l)pai(id*2+1,l,r,sb,x);
	else 
	{
		int p1[30];memset(p1,0,sizeof(p1));
	    int p2[30];memset(p2,0,sizeof(p2));
	    int l1=mid-l+1;int l2=r-mid;
	    if(x==1)
	    {
	    	int i=1,pp=0;
            for(;i<=26;i++)
            {   
    	     if(pp+sb[i]>l1)break;
    	     p1[i]=sb[i];
		     pp+=sb[i];
	        }
	        p1[i]=l1-pp;p2[i]=sb[i]-p1[i];i++;
	        for(;i<=26;i++)p2[i]=sb[i];
	        pai(id*2,l,mid,p1,x);pai(id*2+1,mid+1,r,p2,x);
		}
		if(x==0)
		{
			int i=26,pp=0;
            for(;i>=1;i--)
            {   
    	     if(pp+sb[i]>l1)break;
    	     p1[i]=sb[i];
		     pp+=sb[i];
	        }
	        p1[i]=l1-pp;p2[i]=sb[i]-p1[i];i--;
	        for(;i>=1;i--)p2[i]=sb[i];
		    pai(id*2,l,mid,p1,x);pai(id*2+1,mid+1,r,p2,x);
		}
	} 
	for(int i=1;i<=26;i++)a[id].c[i]=a[id*2].c[i]+a[id*2+1].c[i]; 
}
int main()
{	
	int n,m;
	cin>>n>>m;
	scanf("%s",s+1);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int l,r,x;scanf("%d%d%d",&l,&r,&x);
     	memset(mem,0,sizeof(mem));
     	for(int j=1;j<=26;j++)mem[j]=get(1,l,r,j);
     	if(x==1)pai(1,l,r,mem,1);
		if(x==0)pai(1,l,r,mem,0);		 
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=26;j++)
		 if(get(1,i,i,j))
		 {
		  printf("%c",j+'a'-1);break;	
		 }
	}
	return 0;
}

不知道为啥代码粘过来码风特奇怪,凑合看吧
注意线段树只能有一个延迟标记,如果多个标记会相互覆盖,传递时候出问题,我拍了2h才发现。。。
剩下就是实现了,没啥好说的
还有一种思路是线段树优化桶排序,每次进行26次插修改操作,就转化成基础线段树,不用像我写的那么复杂,常数也小
高老师还有一种将查询修改合一的做法,会更快
往届学长还有直接桶排序碾掉正解的暴力,300ms逆天,人看傻了,吸了氧快到爆,咱不知道为啥,咱也不敢问

T2:Martix

DP不解释
这个状态定义比较迷,反正我是想不到
f[i][j]表示当前处理到第i列,有j个右区间在i及其左边放了1,按列转移,一种不同寻常但有效的思路,以后可以往这方面想
考虑转移方程

\[f_{i,j}+=f_{i-1,j} \]

\[f_{i,j}+=f_{i-1,j-1}\times (r_{i}-l_{i-1}) \]

\[f_{i,j}\times A_{i-j-l_{i-1}}^{l_i-l_{i-1}} \]

第一个就是从不放1的地方转移过来,直接加上
l和r是在i以左,分别是左区间,右区间个数,都是前缀和
如果你要放1,就是在i列前再放一个1,其中已经有了j个1不能重复放,所以r[i]-(j-1)就是可放的位置,乘上即可
上面两个是在右区间转移,下面考虑在左区间放1
你左边一共有i列,已经放了j个右区间的1,还有l[i-1]个左区间在他左边,相当于放了这么多的左区间的1,剩下的就是能放的区间数,l[i]-l[i-1]是你这次要放的1,所以一个排列数
为啥不是组合?

举个例子,一共要选x列放1,其中对于第p行和第q行,你都在第o列放一,当然不是同一种情况

那么式子就有了,注意排列数不要写假

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
int l[3005],r[3005];
int nl[3005],nr[3005];
int f[3005][3005];
int ll[3005],rr[3005];
int jc[3005],ny[3005],jcny[3005];
inline int A(int n,int m)
{
	if(n<m)return 0;
	if(m==0)return 1;
	return jc[n]*jcny[n-m]%mod;//注意不要写假 
}
signed main()
{
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
	  scanf("%lld%lld",&l[i],&r[i]);	
	  nl[l[i]]++;nr[r[i]]++;
	}
	for(int i=1;i<=m;i++)ll[i]=ll[i-1]+nl[i];
	for(int i=1;i<=m;i++)rr[i]=rr[i-1]+nr[i];
	ny[1]=1;jc[0]=jc[1]=1;jcny[0]=1;jcny[1]=1;
    for(int i=2;i<=3000;i++) 
    {
    	jc[i]=jc[i-1]*i%mod;
    	ny[i]=(mod-mod/i)*ny[mod%i]%mod;
    	jcny[i]=jcny[i-1]*ny[i]%mod;
    }
    f[0][0]=1;
    for(int i=1;i<=m;i++)
	 for(int j=0;j<=rr[i];j++)
      { 
		f[i][j]=(f[i][j]+f[i-1][j])%mod;	
      	if(j>0)f[i][j]=(f[i][j]+f[i-1][j-1]*(rr[i]-(j-1))%mod)%mod;
		f[i][j]=f[i][j]*A(i-j-ll[i-1],ll[i]-ll[i-1])%mod;
	  }
	cout<<f[m][n];
	return 0;
}

T3.big

首先看那个式子,他是循环左移,不知道没事,下次就知道了
这个操作是异或一堆数之后循环左移一位再异或一堆数,我们考虑一个结论:
一个数循环左移后异或一个数,等于这个数异或那个数循环左移之后的值,就是

\[a'\oplus b=a\oplus b' \]

很好证,反正是两个数错开一位,所以一样。那么就变成一个数先异或一堆数异或和左移之后的值,再异或后面一堆数
处理前缀,后缀异或和,再处理前缀异或和循环左移后的值,那就有m+1个(可以在最前面或最右面操作)01串,让你求一个数异或其中之一最大,显然trie树,把他们都扔进去,然后查找答案
注意这里对手会让结果最小,所以这个跟直接求不太一样,如果trie树上某一位有0有1,那么对答案没有贡献,因为对手一定会选择答案小的情况,由不得你;同理如果只有01其中之一,就一定有贡献,如果有贡献就把答案的对应那一位或1
dfs求答案,顺便统计,叶子节点方案数是1,如果有一个点选0还是1答案都一样,就把左右方案相加更新这个节点的方案,否则该点方案数等于用来更新答案的子节点的方案数,代码里都有

#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[100005],b[100005],c[100005],d[100005];int n,m;
int trie[3200005][2],tot=1;
int tim[3200005];
void add(int x)
{
	int p=1;
	for(int i=n;i>=1;i--)
	{
		if(!trie[p][x>>(i-1)&1])trie[p][(x>>(i-1))&1]=++tot;
		p=trie[p][(x>>(i-1))&1];
	}
}
int get(int k,int p)
{
	int an=0;tim[p]=1;
	if(k==0)return 0;
  	if(trie[p][0]&&trie[p][1])
	  {
	  	int x1=get(k-1,trie[p][0]),x2=get(k-1,trie[p][1]);
	  	if(x1==x2)tim[p]=tim[trie[p][0]]+tim[trie[p][1]];//两种方案之和 
		if(x1>x2)tim[p]=tim[trie[p][0]];
		if(x1<x2)tim[p]=tim[trie[p][1]];
		an|=max(x1,x2);
	  }
  	else 
  	{
  	  an|=(1<<(k-1));
	  if(trie[p][1])an|=get(k-1,trie[p][1]),tim[p]=tim[trie[p][1]];
	  if(trie[p][0])an|=get(k-1,trie[p][0]),tim[p]=tim[trie[p][0]];
	} 
	return an;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=m;i++)b[i]=b[i-1]^a[i];
	for(int i=m;i>=1;i--)c[i]=c[i+1]^a[i];
	for(int i=1;i<=m;i++)d[i]=((2*b[i]/(1<<n))+2*b[i])%(1<<n);
	for(int i=0;i<=m;i++)add(d[i]^c[i+1]); 
    int ans=get(n,1);
	cout<<ans<<endl<<tim[1];
}  

T4.所驼门王的宝藏

考试时候数据水了,后来加强了数据重测卡掉一堆
思路不难,建边跑tarjan缩点,然后dfs最长路,关键是边特别多,直接n2必炸,要优化
肯定不能用邻接矩阵,前向星的话要有标号,我们开个map,压进去所有宝藏门,把每对坐标映射成一个标号
一行之内所有1门互相连通,一列之内所有2门互相连通,如果建成稠密图要n2,那么我们可以把他建成环,反正最后都要缩成一个点
我们用vector存下每行中所有宝藏门,然后写一个sort,把1门排到最前面,然后扫描,前面的1门都只向后面1个1门连边,最后一个1门向第一个1门连边,环就有了,剩下所有其他门都由最后的1门与之连边。同理2门也这么处理
3门直接暴力建边,查找map中有没有对应的点,有就建边
这里我们把xy坐标当成pair,由于map基于平衡树,所以要重载你结构体比较的运算符;如果用unordered_map的话,要手写哈希,因为自带哈希只能支持基本数据类型,当然更快你也可以自己开哈希表
注意map中[]使用前要先find一下检查存在性
建完图tarjan板子缩点,之后跑dfs,这里要加记忆化才能保证On复杂度xin队NB

#include <bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
	return x*f;
}
struct node{
	int from,next,to;
}a[2000500];
int head[1000005],mm=1;
inline void add(int x,int y)
{
	a[mm].from=x;a[mm].to=y;
	a[mm].next=head[x];head[x]=mm++;
}
int n,r,c;
struct xy{
	int x;int y;int op;
};
struct p{
	int x,y;
};
bool operator<(const p &a1,const p &b1)
{
	return a1.x<b1.x||(a1.x==b1.x&&a1.y<b1.y);
}
map <p,int>ma;
vector <xy>d3; 
vector <xy>h[1000005],z[1000005];
int sh[1000005],sz[1000005];
inline bool cmp1(xy a1,xy b1)//横 
{
	return a1.op<b1.op;  
}
inline bool cmp2(xy a1,xy b1)//纵 
{
	return a1.op>b1.op; 
}
int low[1000005],dfn[1000005];
stack <int> s;bool v[1000005];
int num,scc,ne[1000005],w[1000005];
inline void tarjan(int x)
{
	low[x]=dfn[x]=++num;
	s.push(x);v[x]=1;
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(v[y])low[x]=min(low[x],dfn[y]);
	}
	if(low[x]==dfn[x])
	{
		int tem;scc++;
		do{
			tem=s.top();
			v[tem]=0;
			ne[tem]=scc;w[scc]++;
			s.pop();
		}while(x!=tem);
	}
}
struct newnode{
	int from,to,next;
}b[1000005];
int newhead[1000005],mmm=1;
inline void addd(int x,int y)
{
	b[mmm].from=x;b[mmm].to=y;
	b[mmm].next=newhead[x];newhead[x]=mmm++;
}
int ru[1000005];
int mem[1000005];
int dfs(int x)
{
	if(mem[x])return mem[x];
	int an=0; 
	for(int i=newhead[x];i;i=b[i].next)
	{
		int y=b[i].to;
		an=max(an,dfs(y));
	}
	mem[x]=an+w[x];
	return an+w[x];
}
int main()
{
    cin>>n>>r>>c;
    int m=0;
    for(int i=1;i<=n;i++)
    {
    	int x,y,t;x=read(),y=read(),t=read();
    	xy tmp;tmp.x=x;tmp.y=y;
    	if(t==1)tmp.op=1;if(t==2)tmp.op=3;if(t==3)tmp.op=2;
    	h[x].push_back(tmp);z[y].push_back(tmp);
    	if(t==1)sh[x]++;if(t==2)sz[y]++;
    	if(t==3)d3.push_back(tmp);
    	p pp;pp.x=x,pp.y=y;ma.insert(make_pair(pp,++m));
	}
	for(int i=1;i<=r;i++)
	{
	    if(h[i].empty()||sh[i]==0)continue;
		sort(h[i].begin(),h[i].end(),cmp1);
		int j=0;
	    for(;j<sh[i];j++)
	    {
	  	    if(sh[i]==1)continue;
			p tmp1,tmp2;tmp1.x=i,tmp1.y=h[i][j].y;
	  	    if(j==sh[i]-1)tmp2.x=i,tmp2.y=h[i][0].y;
	  	    else tmp2.x=i,tmp2.y=h[i][j+1].y;
	  	    add(ma[tmp1],ma[tmp2]);
	    }
	    p tmp1,tmp2;tmp1.x=i,tmp1.y=h[i][sh[i]-1].y;
	    for(;j<h[i].size();j++)
	    {
	    	tmp2.x=i,tmp2.y=h[i][j].y;
	    	add(ma[tmp1],ma[tmp2]);
		}
	}
	for(int i=1;i<=c;i++)
	{
		if(z[i].empty()||sz[i]==0)continue;
		sort(z[i].begin(),z[i].end(),cmp2);
		int j=0;
		for(;j<sz[i];j++)
		{
			if(sz[i]==1)continue;
			p tmp1,tmp2;tmp1.y=i,tmp1.x=z[i][j].x;
	  	    if(j==sz[i]-1)tmp2.y=i,tmp2.x=z[i][0].x;
	  	    else tmp2.y=i,tmp2.x=z[i][j+1].x;
	  	    add(ma[tmp1],ma[tmp2]);
		}
		p tmp1,tmp2;tmp1.y=i,tmp1.x=z[i][sz[i]-1].x;
	    for(;j<z[i].size();j++)
	    {
	    	tmp2.y=i,tmp2.x=z[i][j].x;
	    	add(ma[tmp1],ma[tmp2]);
		}
	}
	for(int i=0;i<d3.size();i++)
	{
		p tmp0,tmp;tmp0.x=d3[i].x,tmp0.y=d3[i].y;
		tmp.x=d3[i].x,tmp.y=d3[i].y+1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x,tmp.y=d3[i].y-1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x+1,tmp.y=d3[i].y;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x-1,tmp.y=d3[i].y;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x+1,tmp.y=d3[i].y+1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x+1,tmp.y=d3[i].y-1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]);
		tmp.x=d3[i].x-1,tmp.y=d3[i].y-1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]); 
		tmp.x=d3[i].x-1,tmp.y=d3[i].y+1;if(ma.find(tmp)!=ma.end())add(ma[tmp0],ma[tmp]); 
	}
	for(int i=1;i<=m;i++)if(!dfn[i])tarjan(i);
	for(int x=1;x<=m;x++)
	 for(int i=head[x];i;i=a[i].next)
	 {
	 	int y=a[i].to;
	 	if(ne[x]==ne[y])continue;
		addd(ne[x],ne[y]),ru[ne[y]]++;
	 }
	int ans=0;
	for(int i=1;i<=scc;i++)
	 if(ru[i]==0)ans=max(ans,dfs(i));
	cout<<ans;
	return 0; 
} 

学到了很多stl相关的知识,经验++

考试反思

1.平常知识点要理解深刻,这样才能熟练运用
2.该拿的分还是要拿到,但一定要尝试想一想正解
3.时间分配好,别在一道题耗太久

posted @ 2021-06-07 17:32  D'A'T  阅读(53)  评论(0)    收藏  举报