string
给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]~s[r]升序排序;如果 x=0,将 s[l]~s[r]降序排序。你需要求出最终序列。
$n,m<=10^5$
题解
首先将字符转化为数字,会发现值域为[0,25],而且我们对一段区间排序只需要知道他每个数有多少个即可,
所以考虑桶排+线段树,对于每个节点开一个桶,记录区间数字信息。
提取区间就把桶的信息合并就好了,然后就是修改,因为值域很小,所以排序后对于一个数值就是区间覆盖,于是cover数组就出来了。
考虑提取区间之后就要把原来的数给清掉,一开始是memset,但是其实我们已经用一个结构体存下了需要的信息,所以我们把这一部分减去就好,但是为了不在开结构体变量的时候清0,就需要将三种都写出来。
考虑区间覆盖,基本上是普通操作,要讲的就是更新信息,因为每次都是改一个值,所以没必要都for一遍,但如果之前是memset就需要全部重新计算。值域覆盖区间的贡献就是val*(min(l,a_l)-max(r,a_r)+1),和标记永久化那个有点像,就是交集。
然后你会发现你提取出区间后,他管辖的小区间并不知道,所以还需要在提取区间时搞个标记26(26也不会用到)。
最后输出就遍历线段树就好了,所以我们还需要存这个位置是啥数字,不过只关心叶子结点就好。
我本地跑贼慢,所以修改代码,卡了一年的常数,最后迫不得已(不要脸)地手动开O2,还是没卡进1s,不过上面居然过了,而且事实表明我去掉O2甚至更快(0.6s不到)。
然后还有啥26个线段树,还有区间一样就直接输出什么的,知道就好。
(代码有点乱,还是因为在乱改)
#include<ctime> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define re register const int maxn=100005; int n,m,cnt; char s[maxn]; int root,ls[maxn<<1],rs[maxn<<1],cover[maxn<<1]; struct point{ int num[27],a; }tr[maxn<<1]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } int max(int x,int y){return x>y ? x : y ;} int min(int x,int y){return x<y ? x : y ;} void update(point &ret,point x,point y){ for(re int i=0;i<26;i++) ret.num[i]=x.num[i]+y.num[i]; } void build(int &rt,int l,int r){ rt=++cnt; cover[rt]=-1; if(l==r){ //memset(tr[rt].num,0,sizeof(tr[rt].num)); tr[rt].a=s[l]-'a'; tr[rt].num[s[l]-'a']++; return ; } int mid=(l+r)>>1; build(ls[rt],l,mid); build(rs[rt],mid+1,r); update(tr[rt],tr[ls[rt]],tr[rs[rt]]); } void put_cover(int rt,int l,int r,int val){ cover[rt]=val; tr[rt].a=val; for(re int i=0;i<26;i++) tr[rt].num[i]=0; tr[rt].num[val]=r-l+1; } void push_down(int rt,int l,int r){ int mid=(l+r)>>1; put_cover(ls[rt],l,mid,cover[rt]); put_cover(rs[rt],mid+1,r,cover[rt]); cover[rt]=-1; } point query(int rt,int l,int r,int a_l,int a_r){ if(a_l<=l&&r<=a_r) {point cx=tr[rt];put_cover(rt,l,r,26);return cx;} if(cover[rt]!=-1) push_down(rt,l,r); int mid=(l+r)>>1; point ret; if(a_r<=mid) ret=query(ls[rt],l,mid,a_l,a_r); else if(mid<a_l) ret=query(rs[rt],mid+1,r,a_l,a_r); else update(ret,query(ls[rt],l,mid,a_l,a_r),query(rs[rt],mid+1,r,a_l,a_r)); for(re int i=0;i<26;i++) tr[rt].num[i]-=ret.num[i]; return ret; } void modify(int rt,int l,int r,int a_l,int a_r,int val){ if(a_l<=l&&r<=a_r){ put_cover(rt,l,r,val); return ; } if(cover[rt]!=-1) push_down(rt,l,r); int mid=(l+r)>>1; if(a_l<=mid) modify(ls[rt],l,mid,a_l,a_r,val); if(mid<a_r) modify(rs[rt],mid+1,r,a_l,a_r,val); //update(tr[rt],tr[ls[rt]],tr[rs[rt]]); tr[rt].num[val]+=min(r,a_r)-max(l,a_l)+1; } void get(int rt,int l,int r){ if(l==r){putchar(tr[rt].a+'a');return ;} if(cover[rt]!=-1) push_down(rt,l,r); int mid=(l+r)>>1; get(ls[rt],l,mid); get(rs[rt],mid+1,r); } int main(){ freopen("string.in","r",stdin); freopen("string.out","w",stdout); read(n);read(m); scanf("%s",s+1); build(root,1,n); point cx; for(re int i=0;i<26;i++) cx.num[i]=0; for(re int i=1;i<=m;i++){ int l,r,x; read(l);read(r);read(x); cx=query(root,1,n,l,r); if(x){ for(re int j=0,k=l;j<26&&k<=r;j++) if(cx.num[j]){ modify(root,1,n,k,k+cx.num[j]-1,j); k+=cx.num[j]; cx.num[j]=0; } } else { for(re int j=25,k=l;j>=0&&k<=r;j--) if(cx.num[j]){ modify(root,1,n,k,k+cx.num[j]-1,j); k+=cx.num[j]; cx.num[j]=0; } } } get(root,1,n); return 0; } /* 5 2 cabcd 1 3 1 3 5 0 */
matrix
求出满足以下条件的 n*m 的 01 矩阵个数:
(1) 第 i 行第 1~li 列恰好有 1 个 1。
(2) 第 i 行第 ri~m 列恰好有 1 个 1。
(3) 每列至多有 1 个 1。
补充:(li,ri)不能放
$n,m<=3000,答案对998244353取模$
题解
(DP真是乱来
完全不知道状态咋来的
定义f[i][j]为当前在第i列有j个右区间已经填好了。(有一个题解说是因为每列只能填一个,所以拍成一行,我感觉这个解释好)
lsum[],rsum[]为第i列时左区间端点和右区间端点的个数
然后考虑转移:
首先对于这一列,可以填也可以不填,填的话就是看有多少能填的位置rsum[i]-(j-1) (现在总共的右区间-已经选了的)
我们只考虑了右区间,还有左区间,我们到i的时候有lsum[i]-lsum[i-1]个左区间必须给出位置,美剧处理的右区间个数,剩下的位置有i-j个,那么方案数就乘以这些需要给出位置的左区间的排列。
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int mod=998244353; const int maxn=3005; int n,m; int lsum[maxn],rsum[maxn]; ll f[maxn][maxn];//当前处理到第i列,有j个右区间 template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } int main(){ freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); read(n);read(m); for(int i=1;i<=n;i++){ int x,y; read(x);read(y); lsum[x]++; rsum[y]++; } f[0][0]=1; for(int i=1;i<=m;i++){ f[i][0]=f[i-1][0]; lsum[i]+=lsum[i-1]; rsum[i]+=rsum[i-1]; for(int j=1;j<=i;j++) f[i][j]=(f[i-1][j]+f[i-1][j-1]*(rsum[i]-(j-1)))%mod; //这一列不放,这一列要放有rsum[i]-(j-1)行可以放 for(int k=0;k<=i;k++) for(int j=lsum[i-1];j<lsum[i];j++) f[i][k]=f[i][k]*(i-j-k)%mod; //还有sum[i]-sum[i-1]个左区间要填 //剩下i-k个位置,方案数就是排列 } printf("%d",f[m][n]); }
big
你需要在[0,2^n)中选一个整数 x,接着把 x 依次异或 m 个整数a1~am。在你选出 x 后,你的对手需要选择恰好一个时刻(刚选完数时、异或一些数后或是最后),将 x 变为"$(\left \lfloor \frac{2x}{2^{n}} \right \rfloor+2x)mod 2^{n}$ 。
你想使 x 最后尽量大,而你的对手会使 x 最后尽量小。
你需要求出 x 最后的最大值,以及得到最大值的初值数量。
$n<=30,m<=10^{5},0<=a_{i}<2^{n}$
题解
最暴力的方法就是枚举x,找出变换后的最小值,求最小值的最大值。(没有人一个一个for????可以使用前缀异或和)
考虑他给出的式子前一部分就是找出最高位,后半部分在mod了之后就是将剩下的部分左移一位。所以就相当于转了个圈,比如1234就会变成2341(当然这是假的二进制只是为了说明)
根据位运算的特点,我们可以一开始就这样操作,在i之前异或的数也这样操作,在i之后就异或原数,这样就相当于是在i之后变换。
当然我们也不能一个一个搞,考虑记录后缀异或和suf、变换序列的前缀异或cx,那么对于在i位置变换就是x需要异或的就是cx[i]^suf[i+1],然后考虑把m+1个这样的数丢进trie树。
考虑在trie树上寻找答案:
如果当前节点两个儿子都有,那么无论这一位选啥对手都可以把它变成0。
如果只有0的儿子,那么你选1的话就会得到1。
同理只有1的儿子,选0就会得到1.
函数返回pair<int,int>当前最大值和最大值个数。
遍历一遍就好了。
注意最大值是答案而不是选的数。
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=100005; int n,m; ll mod,tmp; ll a[maxn],cx[maxn],suf[maxn];//cx:变换后的前缀异或和 suf:后缀异或和 int cnt=1,go[maxn*40][2]; void add(int x){ int now=1; for(int i=n-1;i>=0;i--){ bool c=x&(1<<i); if(!go[now][c]) go[now][c]=++cnt; now=go[now][c]; } } pair<int,int> dfs(int now,int s){ if(s==-1) return make_pair(0,1); if(go[now][1]&&go[now][0]){//有两条路,对手能让你这一步得到0,选取能得到的最大的 pair<int,int> x,y; x=dfs(go[now][0],s-1); y=dfs(go[now][1],s-1); if(x.first==y.first) return make_pair(x.first,x.second+y.second); if(x.first>y.first) return x; return y; } else if(!go[now][0]&&go[now][1]){//只有1,这一位填0得到1 pair<int,int> x=dfs(go[now][1],s-1); x.first+=1<<s; return x; } else if(!go[now][1]&&go[now][0]){//只有0,这一位填1得到1 pair<int,int> x=dfs(go[now][0],s-1); x.first+=1<<s; return x; } } int main(){ freopen("big.in","r",stdin); freopen("big.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d",&a[i]); for(int i=m;i;i--) suf[i]=suf[i+1]^a[i]; for(int i=1;i<=m;i++){ cx[i]=(2*a[i]/(1<<n)+2*a[i])%(1<<n); cx[i]^=cx[i-1]; } for(int i=0;i<=m;i++) add(cx[i]^suf[i+1]);//用x异或后得到在i之后变换的答案 pair<int,int> x=dfs(1,n-1); printf("%lld\n%lld",x.first,x.second); return 0; } /* 2 3 1 2 3 */