前言
昨天说好不考试来着,昨晚就晚睡颓了一会,今天遭报应了,也没好好考,考得挺烂的就不多说了。
T1 string
题目描述
给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]~s[r]升序排序;如果 x=0,将 s[l]~s[r]降序排序。你需要求出最终序列。
输入格式
第一行两个整数 n,m。
第二行一个字符串 s。
接下来 m 行每行三个整数 l,r,x。
输出格式
一行一个字符串表示答案。
样例
样例输入
5 2
cabcd
1 3 1
3 5 0
样例输出
abdcc
数据范围与提示
对于 40%的数据,n,m<=1000。
对于 100%的数据,n,m<=100000。
解题思路
比赛上第一想法就是打一发sort,直接暴力,然后完美TLE40pts,这一部分分也是所有人都拿到了,没什么意义。。
正解是线段树,主流打法有两种:
- 开24棵线段树,分别对26种字母进行维护。
- 只开一棵线段树,对于区间维护求值。
我当然是选择码量较小并且快的第二种了QAQ,但是聪明睿智的@WindZR选择了第一种打法(code),虽然他多加了好几个inline快了2s才过。
对于每一个线段树区间,如果这个区间都为一个字母,存储。否则,就为0.
在更改的时候,先求一下本区间中各个字母的多少,然后分别对于线段树中的每个字母更改,直接存延迟标记到数值上就好了。
最后统一push_down一下,输出就好了。代码实现可能有一点考验码力。。
code
#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e5+10;
int tre[N<<2];
int n,m,len[27];
char s[N];
inline void push_down(int x)
{
if(tre[x])
tre[ls]=tre[rs]=tre[x];
tre[x]=0;
}
inline void push_up(int x)
{
tre[x]=tre[ls]*(tre[ls]==tre[rs]);
}
inline void build(int x,int l,int r)
{
// cout<<x<<' '<<l<<' '<<r<<endl;
if(l==r)
{
tre[x]=s[l]-'a'+1;
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
push_up(x);
}
inline void query(int x,int l,int r,int L,int R)
{
// cout<<x<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<endl;
if(L>R||r<L||l>R)
return ;
if(L<=l&&r<=R&&tre[x])
{
len[tre[x]-1]+=r-l+1;
return ;
}
push_down(x);
int mid=(l+r)>>1;
if(L<=mid)
query(ls,l,mid,L,R);
if(R>mid)
query(rs,mid+1,r,L,R);
// push_up(x);
}
inline void update(int x,int l,int r,int L,int R,int dat)
{
// cout<<x<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<' '<<dat<<endl;
if(L>R||r<L||l>R)
return ;
if(L<=l&&r<=R||tre[x]==dat)
{
tre[x]=dat;
return ;
}
push_down(x);
int mid=(l+r)>>1;
if(L<=mid)
update(ls,l,mid,L,R,dat);
if(R>mid)
update(rs,mid+1,r,L,R,dat);
push_up(x);
}
inline void Push_down(int x,int l,int r)
{
if(tre[x])
{
for(int i=l;i<=r;i++)
s[i]=tre[x]+'a'-1;
return ;
}
int mid=(l+r)>>1;
Push_down(ls,l,mid);
Push_down(rs,mid+1,r);
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
build(1,1,n);
for(int i=1,x,y,vis;i<=m;i++)
{
memset(len,0,sizeof(len));
scanf("%d%d%d",&x,&y,&vis);
query(1,1,n,x,y);
/*for(int j=0;j<26;j++)
cout<<len[j]<<' ';
cout<<endl;*/
if(vis)
{
int temp=x;
for(int j=0;j<26;j++)
{
if(len[j])
update(1,1,n,temp,temp+len[j]-1,j+1);
temp+=len[j];
}
}
else
{
int temp=x;
for(int j=25;j>=0;j--)
{
if(len[j])
update(1,1,n,temp,temp+len[j]-1,j+1);
temp+=len[j];
}
}
}
Push_down(1,1,n);
for(int i=1;i<=n;i++)
printf("%c",s[i]);
return 0;
}
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。
输入格式
第一行两个整数 n,m。接下来 n 行每行 2 个整数 li,ri。
输出格式
一行一个整数表示答案。对 998244353 取模
样例
样例输入
2 6
2 4
5 6
样例输出
12
数据范围与提示
对于 20%的数据,n,m<=12。
对于 40%的数据,n,m<=50。
对于 70%的数据,n,m<=300。
对于 100%的数据,n,m<=3000,1<=li<ri<=m。
解题思路
说实话看这题第一眼,我想的是排列组合,后来想了想是个\(n^2\)的dp,但是我想的是枚举坐标,所以整不出来了,在然后就直接搜呗(逃。
这个题正解特别妙,\(f[i][j]\) ,i表示第几列,j表示第i列后面1的个数,最重要的一点是这个题是逆推的。由\((0,0)\)开始,且该位置方案数为1。然后就开始愉快地实现了。
第一部分
显然可以从i-1列转移过来,状态有两种:放与不放,也就是j-1或者 j,利用r总数进行更新。
第二部分
枚举前一行到当前行的可行的左侧数量,再进行枚举左侧放的棋子数然后再乘上一个方案数就好了。
- 优化:l和r边界的总数用前缀和优化。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,M=4e3+10,mod=998244353;
int n,m,sl[N],sr[N],f[M][M];
#undef int
int main()
{
#define int register long long
#define ll long long
scanf("%lld%lld",&n,&m);
for(int i=1,l,r;i<=n;i++)
{
scanf("%lld%lld",&l,&r);
sl[l]++;
sr[r]++;
}
for(int i=1;i<=m;i++)
{
sl[i]+=sl[i-1];
sr[i]+=sr[i-1];
// cout<<sl[i]<<' '<<sr[i]<<endl;
}
f[0][0]=1;
for(int i=1;i<=m;i++)
{
f[i][0]=f[i-1][0];
for(int j=1;j<=i;j++)
f[i][j]=(f[i-1][j]+f[i-1][j-1]*(sr[i]-j+1)%mod)%mod;
for(int j=sl[i-1];j<sl[i];j++)
for(int k=0;k<=i;k++)
f[i][k]=f[i][k]*(i-j-k)%mod;
}
printf("%lld",f[m][n]);
return 0;
}
```# 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。
## 输入格式
第一行两个整数 n,m。接下来 n 行每行 2 个整数 li,ri。
## 输出格式
一行一个整数表示答案。对 998244353 取模
## 样例
### 样例输入
```cpp
2 6
2 4
5 6
样例输出
12
数据范围与提示
对于 20%的数据,n,m<=12。
对于 40%的数据,n,m<=50。
对于 70%的数据,n,m<=300。
对于 100%的数据,n,m<=3000,1<=li<ri<=m。
解题思路
说实话看这题第一眼,我想的是排列组合,后来想了想是个\(n^2\)的dp,但是我想的是枚举坐标,所以整不出来了,在然后就直接搜呗(逃。
这个题正解特别妙,\(f[i][j]\) ,i表示第几列,j表示第i列后面1的个数,最重要的一点是这个题是逆推的。由\((0,0)\)开始,且该位置方案数为1。然后就开始愉快地实现了。
第一部分
显然可以从i-1列转移过来,状态有两种:放与不放,也就是j-1或者 j,利用r总数进行更新。
第二部分
枚举前一行到当前行的可行的左侧数量,再进行枚举左侧放的棋子数然后再乘上一个方案数就好了。
- 优化:l和r边界的总数用前缀和优化。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10,M=4e3+10,mod=998244353;
int n,m,sl[N],sr[N],f[M][M];
#undef int
int main()
{
#define int register long long
#define ll long long
scanf("%lld%lld",&n,&m);
for(int i=1,l,r;i<=n;i++)
{
scanf("%lld%lld",&l,&r);
sl[l]++;
sr[r]++;
}
for(int i=1;i<=m;i++)
{
sl[i]+=sl[i-1];
sr[i]+=sr[i-1];
// cout<<sl[i]<<' '<<sr[i]<<endl;
}
f[0][0]=1;
for(int i=1;i<=m;i++)
{
f[i][0]=f[i-1][0];
for(int j=1;j<=i;j++)
f[i][j]=(f[i-1][j]+f[i-1][j-1]*(sr[i]-j+1)%mod)%mod;
for(int j=sl[i-1];j<sl[i];j++)
for(int k=0;k<=i;k++)
f[i][k]=f[i][k]*(i-j-k)%mod;
}
printf("%lld",f[m][n]);
return 0;
}
T3 big
题目描述
你需要在\([0,2)\)中选一个整数 x,接着把 x 依次异或 m 个整数a1~am。
在你选出 x 后,你的对手需要选择恰好一个时刻(刚选完数时、异或一些数后或是最后),将 x 变\((\lfloor\dfrac{2x}{2^n}\rfloor+2x) \bmod 2^n\) 你想使 x 最后尽量大,而你的对手会使 x 最后尽量小。
你需要求出 x 最后的最大值,以及得到最大值的初值数量。
输入格式
第一行两个整数 n,m。第二行 m 个整数 a1~am
输出格式
第一行输出一个整数,表示 x 最后的最大值。
第二行输出一个整数,表示得到最大值的初值数量。
第一个数正确得 6 分,两个数都正确再得 4 分
样例
样例输入
2 3
1 2 3
样例输出
1
2
解题思路
考试的时候,我一开始都没打算打这个题(还是我太菜了),联想到了博弈论这一类奇奇怪怪的东西。。。后来仔细看了看题目中操作的式子,想到了循环节,也想到了分位运算,但是就是没有想到用Tire树 [流泪].jpg
\((\lfloor\dfrac{2x}{2^n}\rfloor+2x) \bmod 2^n\),这个式子一看就不是瞎整的,有二进制的感觉,\(\lfloor\dfrac{2x}{2^n}\rfloor\) 就是\((x>>(n-1))\)(取第n位)与\(x<<1\)的加和再按位与上\(1<<n\),扣下前n位,总的来看就类似与把110101变成了101011。
我们再求一个一个前缀和后缀,后缀用于维护u对手操作完没变的点,前缀维护操作过的点,不难发现,对于i之前的所有的都操作与将i之前的异或再操作结果是一样的。
可以手动模拟一下。
然后就是构建Tire树,从根开始往下遍历每一位,处理情况有3种
- 子节点0和1都有:此时如果我选择0,那么对手就会选择1,同样的,如果我选择1,那么对手就会选择0,因此这个点对于答案是没有贡献的,只需要继承上来就好了。
- 只有0节点:只可以走这一路,对答案有贡献\(1<<res\)
- 只有1节点:与只有0节点的情况大同小异,贡献也是\(1<<res\)
- 优化:遍历时可以返回一个pair同时储存两个答案。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,s[N],suf[N],he[N];
int cnt=1,tre[N*40][2];
int work(int x)
{
// return 2*x/(1<<n)+2*x%(1<<n);
return ((x>>(n-1))+(x<<1))&((1<<n)-1);
}
inline void Tire_Add(int x)
{
int now=1;
for(int i=n-1;i>=0;i--)
{
bool p=x&(1<<i);
if(!tre[now][p])
tre[now][p]=++cnt;
now=tre[now][p];
// cout<<now<<' ';
}
}
pair<int,int> dfs(int now,int res)
{
if(res==-1)
return make_pair(0,1);
if(tre[now][0]&&tre[now][1])
{
pair<int,int> x=dfs(tre[now][0],res-1),y=dfs(tre[now][1],res-1);
if(x.first==y.first)
return make_pair(x.first,x.second+y.second);
if(x.first>y.first)
return x;
return y;
}
if(tre[now][0])
{
pair<int,int> x=dfs(tre[now][0],res-1);
x.first+=(1<<res);
return x;
}
if(tre[now][1])
{
pair<int,int> x=dfs(tre[now][1],res-1);
x.first+=(1<<res);
return x;
}
}
#undef int
int main()
{
#define int long long
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
scanf("%lld",&s[i]);
for(int i=m;i>=1;i--)
{
suf[i]=suf[i+1]^s[i];
// cout<<suf[i]<<' ';
}
for(int i=1;i<=m;i++)
{
he[i]=work(s[i])^he[i-1];
// cout<<he[i]<<' ';
}
for(int i=0;i<=m;i++)
Tire_Add(he[i]^suf[i+1]);
pair<int,int> ans=dfs(1,n-1);
printf("%lld\n%lld",ans.first,ans.second);
return 0;
}
T4 P2403 [SDOI2010]所驼门王的宝藏
解题思路
算是这次比赛里比较好想的一个题了 (虽然我打的裸暴力)。
正解的话就是Tarjan缩点+DAG。
主流打法有两种:
- 建假点,利用假点运算
- sort排序,暴力建边 (我当然是喜欢暴力的了)
对于自动排序这一类的问题,hash和map都可以做,(我当然是选择暴力的map了)题面里的所谓各种门,无非是连接各个点的一条边,显然,对于一系列的点如果我们可以到达其中的一个就一定可以全部到达,这不就是强联通分量吗。
因此我们对于每一种门分别查找建出边来,
建边
横天门与纵寰门
这两种门是大同小异的,这里举横天门的例子,先将所有的宝藏室排个序(第一关键字是横坐标,第二关键字是操作,第三关键字是总纵坐标)。
- 优化:操作的关键字,先判y的比先判x的快了一半(先判x也没事 ~
毕竟洛谷数据水嘛)。
任意门
直接暴力枚举各个点建出边来就好了,分别扫一下八个方向是否有点就好了。
其他操作
接下来就可以套Tarjan的板子缩点 了,顺便储存一下各个点属于那一个强联通分量,处理入度为拓扑排序做准备,然后再进行dfs拓扑排序,求出最大值就ok了。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,t,ans,du[N],f[N];
int tot,head[N],ver[N*100],nxt[N*100],fro[N*100];
int cnt,tim,dfn[N],low[N],bel[N],sum[N];
int top,sta[N];
bool bosta[N];
map<int,bool> vis[N];
map<int,int> s[N];
map<int,bool>::iterator it;
int sx[9]={0,-1,-1,-1,0,0,1,1,1};
int sy[9]={0,-1,0,1,-1,1,-1,0,1};
struct Ques
{
int x,y,opt,id;
}q[N];
inline bool comp1(Ques x,Ques y)
{
if(x.x!=y.x)
return x.x<y.x;
if(y.opt==1)
return false;
if(x.opt==1)
return true;
return x.y<y.y;
}
inline bool comp2(Ques x,Ques y)
{
if(x.y!=y.y)
return x.y<y.y;
if(y.opt==2)
return false;
if(x.opt==2)
return true;
return x.x<y.x;
}
inline void add_edge(int x,int y)
{
ver[++tot]=y;
fro[tot]=x;
nxt[tot]=head[x];
head[x]=tot;
}
inline void Tarjan(int x)
{
dfn[x]=low[x]=++tim;
sta[++top]=x;
bosta[x]=true;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(!dfn[to])
{
Tarjan(to);
low[x]=min(low[x],low[to]);
}
else if(!bel[to])
low[x]=min(low[x],low[to]);
}
if(dfn[x]==low[x])
{
cnt++;
int temp=sta[top--];
bel[temp]=cnt;
sum[cnt]++;
bosta[temp]=false;
while(temp!=x)
{
temp=sta[top--];
bel[temp]=cnt;
sum[cnt]++;
bosta[temp]=false;
}
}
}
inline void dfs(int x,int fa)
{
if(f[x]>sum[x])
return ;
f[x]=sum[x];
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(to==fa)
continue;
dfs(to,x);
f[x]=max(f[x],f[to]+sum[x]);
}
}
inline int read()
{
char ch=getchar();
int x=0;
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
int main()
{
// scanf("%d%d%d",&t,&n,&m);
#define int register int
t=read();
n=read();
m=read();
for(int i=1;i<=t;i++)
{
// scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].opt);
q[i].x=read();
q[i].y=read();
q[i].opt=read();
q[i].id=i;
s[q[i].x][q[i].y]=i;
}
#define head wzrdsb
int head=1,tail=1;
sort(q+1,q+t+1,comp1);
for(int i=1;i<=t;i++)
{
if(q[i].x!=q[i+1].x)
{
if(head!=tail)
add_edge(q[tail].id,q[head].id);
tail=head=i+1;
}
else
{
if(q[tail].opt==1)
add_edge(q[tail].id,q[i+1].id);
if(q[i+1].opt==1)
tail=i+1;
if(q[head].opt!=1)
tail=head=i+1;
}
}
head=1;
tail=1;
sort(q+1,q+t+1,comp2);
for(int i=1;i<=t;i++)
{
if(q[i].y!=q[i+1].y)
{
if(head!=tail)
add_edge(q[tail].id,q[head].id);
tail=head=i+1;
}
else
{
if(q[tail].opt==2)
add_edge(q[tail].id,q[i+1].id);
if(q[i+1].opt==2)
tail=i+1;
if(q[head].opt!=2)
tail=head=i+1;
}
}
#undef head
for(int i=1;i<=t;i++)
if(q[i].opt==3)
{
for(int j=1;j<=8;j++)
if(s[q[i].x+sx[j]].count(q[i].y+sy[j]))
add_edge(q[i].id,s[q[i].x+sx[j]][q[i].y+sy[j]]);
}
for(int i=1;i<=t;i++)
if(!dfn[i])
Tarjan(i);
for(int i=1;i<=tot;i++)
{
int to=ver[i];
if(bel[fro[i]]!=bel[to])
vis[bel[fro[i]]][bel[to]]=true;
}
memset(head,0,sizeof(head));
tot=0;
for(int i=1;i<N;i++)
for(it=vis[i].begin();it!=vis[i].end();it++)
{
add_edge(i,it->first);
du[it->first]++;
}
for(int i=1;i<=cnt;i++)
if(!du[i])
{
dfs(i,0);
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}