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,按列转移,一种不同寻常但有效的思路,以后可以往这方面想
考虑转移方程
第一个就是从不放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
首先看那个式子,他是循环左移,不知道没事,下次就知道了
这个操作是异或一堆数之后循环左移一位再异或一堆数,我们考虑一个结论:
一个数循环左移后异或一个数,等于这个数异或那个数循环左移之后的值,就是
很好证,反正是两个数错开一位,所以一样。那么就变成一个数先异或一堆数异或和左移之后的值,再异或后面一堆数
处理前缀,后缀异或和,再处理前缀异或和循环左移后的值,那就有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.时间分配好,别在一道题耗太久

浙公网安备 33010602011771号