noip模拟5
T1
题目描述:
给定一个由小写字母组成的字符串 \(s\)。有 \(m\) 次操作,每次操作给定 3 个参数 \(l\),\(r\),\(x\)。如果 \(x\)=1,将 \(s[l]\) ~ \(s[r]\)升序排序;如果 \(x\)=0,将 \(s[l]\) ~ \(s[r]\)降序排序。你需要求出最终序列。
考试时想到可以用线段树维护了,但是蒟弱实属不会实现,于是就打了一发暴力sort,骗得40而归~
考虑正解吧。用一颗维护26个值的线段树,实现区间的修改与查询,最后单点查询输出即可。
坑点:
-
既然是区间修改问题,那么懒标记在大部分时候都要派上用场,不要忘记下放;
-
本来想节省点空间开\(son[26]\)呢,忘了'a'-'a'==0这茬,所以在调试时吃了大亏,和T4一样,特特特别感谢各位同机房大佬们对蒟弱的大力支持。
好了,开始讨论。每次面对一个指令,我们先使用桶排序储存区间内每个数的个数,然后利用桶排序的分类特性将各个数正序或倒序放回线段树即可。
利用lz懒标记来储存本区间内将要修改成的数,然后就是简单的线段树板子惹:
Code
#include<bits/stdc++.h>
using namespace std;
namespace EMT{
int read(){int x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x;}
#define F(i,a,b) for(register int i=a;i<=b;i++)
#define pf printf
void pi(int x){pf("%d ",x);}void pn(){pf("\n");}void ps(int x[],int size){F(i,1,size)pi(x[i]);pn();}void pt(){pf(" ");}
const int N=1e5+100;
int n,m,a[N],cnt[26];char s[N];struct tree{int l,r,sum[26],lz;}t[N<<2];
inline void up(int p){F(i,0,25)t[p].sum[i]=t[p*2].sum[i]+t[p*2+1].sum[i];}
inline void down(int p){
if(t[p].lz!=-1){
int k=t[p].lz;t[p].lz=-1;
t[p*2].lz=t[p*2+1].lz=k;
F(i,0,25)t[p*2].sum[i]=t[p*2+1].sum[i]=0;
t[p*2].sum[k]+=t[p*2].r-t[p*2].l+1;
t[p*2+1].sum[k]+=t[p*2+1].r-t[p*2+1].l+1;
}
}
inline void build(int p,int l,int r){
t[p].l=l,t[p].r=r;t[p].lz=-1;
if(l==r){t[p].sum[a[l]]++;return;}
int mid=(l+r)>>1;
build(p*2,l,mid);build(p*2+1,mid+1,r);
up(p);
}
inline void ask(int p,int l,int r){
if(t[p].l>=l&&t[p].r<=r){F(i,0,25)cnt[i]+=t[p].sum[i];return;}
down(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid)ask(p*2,l,r);
if(r>mid)ask(p*2+1,l,r);
}
inline int Ask(int p,int x){
if(t[p].l==t[p].r){
F(i,0,25)if(t[p].sum[i])return i;
}down(p);
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid)return Ask(p*2,x);
else return Ask(p*2+1,x);
}
inline void change(int p,int l,int r,int w){
if(l>r)return;
if(t[p].l>=l&&t[p].r<=r){
F(i,0,25)t[p].sum[i]=0;
t[p].sum[w]=t[p].r-t[p].l+1;
t[p].lz=w;return;
}
down(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid)change(p*2,l,r,w);
if(r>mid)change(p*2+1,l,r,w);
up(p);
}
inline short main(){
n=read();m=read();scanf("%s",s+1);F(i,1,n)a[i]=s[i]-'a';
build(1,1,n);
F(T,1,m){
memset(cnt,0,sizeof(cnt));
int l=read(),r=read(),x=read();
ask(1,l,r);
if(x){
int last=l;
F(i,0,25)if(cnt[i]){change(1,last,last+cnt[i]-1,i);last+=cnt[i];}
}
else{
int last=l;
for(register int i=25;i>=0;i--)if(cnt[i]){change(1,last,last+cnt[i]-1,i);last+=cnt[i];}
}
}
F(i,1,n)pf("%c",Ask(1,i)+'a');return 0;
}
}
signed main(){return EMT::main();}
T2 matrix
题目:
求出满足以下条件的 \(n*m\) 的 01 矩阵个数:
(1)第 i 行第 1~\(li\) 列恰好有 1 个 1。 (\(li\)+1到\(ri\)-1不能放1) (2)第 \(i\) 行第 \(ri\)~\(m\) 列恰好有 1 个 1。
(3)每列至多有 1 个 1。
暴搜才骗到20分,差评。分列进行枚举
设\(f[i][j]\)表示目前dp到第i列,有j个右区间的点放在了i列左端,
考虑两种情况:
1.这一列不放,那么\(f[i][j]=f[i-1][j]\).
2.这一列放,那么有\(r[i]-(j-1)\)个点可供挑选,\(f[i][j]=f[i-1][j-1]*(r[i]-(j-1))\).
举个例子,一共要选x列放1,其中对于第p行和第q行,都在第o列放一,当然不是同一种情况,所以要考虑乘上一个排列,\(f[i][j]*=_{l[i]-l[i-1]}^{i-j-l[i-1]}\textrm{A}\)
其中,\(l[i]\)和\(r[i]\)分别表示左区间在i及i列以左的区间总数和右区间在i列及其以左的区间总数。
最后的答案就是f[m][n]
Code
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
namespace EMT{
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int read(){int x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x;}
#define pf printf
#define F(i,a,b) for(register int i=a;i<=b;i++)
#define int long long
inline void pi(int x){pf("%lld ",x);}inline void pt(){pf(" ");}inline void pn(){pf("\n");}inline void ps(int x[],int size){F(i,1,size)pi(x[i]);pn();}
const int N=3005,mod=998244353;
int tot,n,m,l[N],r[N],f[N][N];bool use[N];
signed main(){
n=read();m=read();if(2*n>m){pi(0);return 0;}
F(i,1,n){int x=read(),y=read();l[x]++;r[y]++;}
f[0][0]=1;
F(i,1,m)l[i]+=l[i-1],r[i]+=r[i-1];
F(i,1,m){
f[i][0]=f[i-1][0];
F(j,1,i)f[i][j]=(f[i-1][j]+f[i-1][j-1]*(r[i]-j+1))%mod;
F(j,l[i-1],l[i]-1)F(k,0,i)f[i][k]=f[i][k]*(i-j-k)%mod;
}
pi(f[m][n]);
return 0;
}
}
signed main(){return EMT::main();}
T3 big
考试时想到建0/1 trie树了,也考虑到先后左移的问题了,就是不知道一开始要把什么东西投进去,没想到是个0。。。
首先考虑对手什么时候操作。
\(x\)异或之后再左移1位,等价于将之前异或过的数都左移1位,再直接异或\(x\)左移1位。
所以可以预处理出所有的左移序列投到trie树上。
然后考虑子结点对自己的影响。
设\(now\)为遍历到某个点时得到的值,
如果有0/1两个子节点,那么对手一定会选择不让你的值增大,也就是说无法对\(now\)产生贡献,now<<=1;
如果只有1个,那么我们一定能得到使该点产生贡献的数(因为任选),
即now=(now<<1)+1
如果没有,说明遍历完毕,记录更新maxn和cnt。
Code
#include<bits/stdc++.h>
using namespace std;
namespace EMT{
int read(){int x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x;}
#define int long long
#define F(i,a,b) for(register int i=a;i<=b;i++)
#define pf printf
void pi(int x){pf("%lld ",x);}void pn(){pf("\n");}void ps(int x[],int size){F(i,1,size)pi(x[i]);pn();}void pt(){pf(" ");}
struct tree{
int son[2];
}tir[100000000];int num,maxx=-1,cnt,n,m;
int w(int x){
int cntt=0;
while(x){
x-=x&(-x);
cntt++;
}return cntt;
}
void ins(int k){
int now=0;
for(int i=n-1,j;i>=0;i--){
j=(k>>i)&1;
if(!tir[now].son[j])tir[now].son[j]=++num;
now=tir[now].son[j];
}
}const int N=1e5+100;
int a[N];
int ope(int x){return (((2*x)/(1<<n))+2*x)%(1<<n);}
void dfs(int now,int tot){
if(tir[now].son[0]&&tir[now].son[1]){
dfs(tir[now].son[0],tot<<1);
dfs(tir[now].son[1],tot<<1);
return;
}
if(tir[now].son[0]&&!tir[now].son[1]){
dfs(tir[now].son[0],(tot<<1)+1);
return;
}
if(!tir[now].son[0]&&tir[now].son[1]){
dfs(tir[now].son[1],(tot<<1)+1);
return;
}
if(maxx==tot)cnt++;
if(maxx<tot)maxx=tot,cnt=1;
}
signed main(){
n=read();m=read();
F(i,1,m)a[i]=read()^a[i-1];
F(i,0,m){int x=a[m]^a[i];ins(x^ope(a[i]));}
dfs(0,0);pi(maxx);pi(cnt);
}
}
signed main(){return EMT::main();}
T4 所驼门王的宝藏
题目描述:
千算万算没想到,竟然是sort比较函数出了锅。。。
有向图,我们考虑将有用的点用tarjan缩成几个强联通分量,利用拓扑排序来求得ans。
首先建边是个难题。分行、列、周围建边,由于一行中的横天门只要到达一个就能到达所有,所以只需要每个门和本行另外一条门联通即可。
对于行:
inline bool xcom(point a,point b){
if(a.x!=b.x)return a.x<b.x;
if(a.kd==b.kd&&a.kd==1)return a.y<b.y;
if(a.kd==1)return 1;if(b.kd==1)return 0;
return a.y<b.y;
}
inline void opex(){
sort(p+1,p+n+1,xcom);
int first=1,last=1;
for(register int i(1);i<=n;++i){
if(p[i].x!=p[i+1].x){
if(first!=last)
add(p[last].id,p[first].id);
first=last=i+1;
}
else{
if(p[last].kd==1)add(p[last].id,p[i+1].id);
if(p[i+1].kd==1)last=i+1;
if(p[first].kd!=1)last=first=i+1;
}
}
}
对于列:
inline bool ycom(point a,point b){
if(a.y!=b.y)return a.y<b.y;
if(a.kd==b.kd&&a.kd==2)return a.y<b.y;
if(a.kd==2)return 1;if(b.kd==2)return 0;
return a.y<b.y;
}
inline void opey(){
sort(p+1,p+n+1,ycom);
int first=1,last=1;
for(register int i(1);i<=n;++i){
if(p[i].y!=p[i+1].y){
if(first!=last)add(p[last].id,p[first].id);
first=last=i+1;
}
else{
if(p[last].kd==2)add(p[last].id,p[i+1].id);
if(p[i+1].kd==2)last=i+1;
if(p[first].kd!=2)last=first=i+1;
}
}
}
对于周围:
int X[10]={0,1,1,0,0,1,-1,-1,-1};
int Y[10]={0,0,1,1,-1,-1,1,0,-1};
inline void opez(){
F(i,1,n){
if(p[i].kd==3){
F(j,1,8)if(hh.count(pa(p[i].x+X[j],p[i].y+Y[j])))add(p[i].id,hh[pa(p[i].x+X[j],p[i].y+Y[j])]);
}
}
}
建完边,跑完tj后就可以愉快拓扑了~
Code
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;
namespace EMT{
int read(){int x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x;}
const int N=1e5+10;int cntk,n,co,head[N],pos[N],dfn[N],low[N],dp[N],num,in[N],size[N];stack<int>s;queue<int>q;bool ins[N];
struct point{int id,x,y,kd;}p[N];struct node{int next,from,to;}e[N<<4];
map<pair<int,int>,int>hh;
void add(int next,int to){e[++co].next=head[next],e[co].from=next,e[co].to=to,head[next]=co;}
inline bool xcom(point a,point b){
if(a.x!=b.x)return a.x<b.x;
if(a.kd==b.kd&&a.kd==1)return a.y<b.y;
if(a.kd==1)return 1;if(b.kd==1)return 0;
return a.y<b.y;
}
inline bool ycom(point a,point b){
if(a.y!=b.y)return a.y<b.y;
if(a.kd==b.kd&&a.kd==2)return a.y<b.y;
if(a.kd==2)return 1;if(b.kd==2)return 0;
return a.y<b.y;
}
inline void opex(){
sort(p+1,p+n+1,xcom);
int first=1,last=1;
for(register int i(1);i<=n;++i){
if(p[i].x!=p[i+1].x){
if(first!=last)
add(p[last].id,p[first].id);
first=last=i+1;
}
else{
if(p[last].kd==1)add(p[last].id,p[i+1].id);
if(p[i+1].kd==1)last=i+1;
if(p[first].kd!=1)last=first=i+1;
}
}
}
inline void opey(){
sort(p+1,p+n+1,ycom);
int first=1,last=1;
for(register int i(1);i<=n;++i){
if(p[i].y!=p[i+1].y){
if(first!=last)add(p[last].id,p[first].id);
first=last=i+1;
}
else{
if(p[last].kd==2)add(p[last].id,p[i+1].id);
if(p[i+1].kd==2)last=i+1;
if(p[first].kd!=2)last=first=i+1;
}
}
}
int X[10]={0,1,1,0,0,1,-1,-1,-1};
int Y[10]={0,0,1,1,-1,-1,1,0,-1};
inline void opez(){
F(i,1,n){
if(p[i].kd==3){
F(j,1,8)if(hh.count(pa(p[i].x+X[j],p[i].y+Y[j])))add(p[i].id,hh[pa(p[i].x+X[j],p[i].y+Y[j])]);
}
}
}
inline void tj(int x){
dfn[x]=low[x]=++num;
s.push(x),ins[x]=1;
for(register int i(head[x]),j;i;i=e[i].next){
j=e[i].to;
if(!dfn[j])tj(j),low[x]=min(low[x],low[j]);
else if(ins[j])low[x]=min(low[x],dfn[j]);
}
if(dfn[x]==low[x]){
++cntk;int y;
do{
y=s.top(),s.pop(),ins[y]=0;
pos[y]=cntk,++size[cntk];
}while(x!=y);
}
}
int main(){
n=read(),read(),read();
for(register int i(1);i<=n;++i){p[i].x=read(),p[i].id=i,p[i].y=read(),p[i].kd=read(),hh[pair<int,int>(p[i].x,p[i].y)]=i;}
opex(),opey(),opez();
for(register int i(1);i<=n;++i)if(!dfn[i])tj(i);
hh.clear();int Co=co;co=0;memset(head,0,sizeof(head));
for(register int i(1);i<=Co;++i){
int x=e[i].from,y=e[i].to;
if(pos[x]!=pos[y])add(pos[x],pos[y]),++in[pos[y]];
}int ans(0);
for(register int i(1);i<=cntk;++i){if(!in[i])q.push(i);dp[i]=size[i];}
while(!q.empty()){
int u=q.front();q.pop();
for(register int i=head[u],j;i;i=e[i].next){
j=e[i].to,in[j]--;
dp[j]=dp[j]>dp[u]+size[j]?dp[j]:dp[u]+size[j];
if(!in[j])q.push(j);
}
}
for(register int i(1);i<=cntk;++i)ans=ans>dp[i]?ans:dp[i];
printf("%d\n",ans);return 0;
}
}
int main(){return EMT::main();}