2023.8.28
A
长为 \(n=2^k-1\) 的纸条,编号为 \([0,n-1)\),将纸条对折 \(k\) 次(每次将右边翻转至左边下面),记形成的序列为 \(\{a_n\}\).
\(m\) 次询问,给定 \(l,r\) 求解:
若 \(l\) 为偶数,那么先计算 \(+\),否则先计算 \(\oplus\).
为减少读入量,进行如下操作:
-
\(l,r\) 为初始给定的值,\(h=0\).
-
执行 \(m\) 次,令:
问最后 \(h\) 的值。
\(0\le k\le 30\),\(1\le m\le 5\times 10^7\),\(0\le a,b,c< 2^{30}\).
看下 \(k=3\) 是怎么折的。
即 \(\{a_n\}=\{0,7,4,3,2,5,6,1\}\).
直接观察可以发现每两项的和都是 \(7\),不难猜到每对偶位和奇位的和是 \(2^k-1\).
然后再观察这个 \(F(l,r)\) 在做什么。
\(l\) 为偶数:
那么会产生 \(\lfloor\frac{r-l}{2}\rfloor\) 个 \(2^k-1\),再考虑 \(r\) 位的值。
\(l\) 为奇数:
会产生 \(\lfloor\frac{r-l-1}{2}\rfloor\) 个 \(2^k-1\),加上 \(a_l\) 的值,再考虑 \(a_r\).
再来看怎么求 \(a_x\),容易手推出来一个单次 \(O(k)\) 的做法,但是不够理想。
我们需要一个单次 \(O(1)\) 的做法。
将折纸过程对半预处理,预处理出纸条折叠 \(\lfloor\frac{k}{2}\rfloor\) 次时每一层的数字区间,和纸叠展开 \(\lceil\frac{k}{2}\rceil\) 次后位置的对应关系。
对应关系仅和所求位置的前 \(\lceil\frac{k}{2}\rceil\) 位有关。
时间复杂度 \(O(2^{\lceil\frac{k}{2}\rceil})+m\).
这些东西的处理就看起来比较玄学了。
#include<bits/stdc++.h>
#define ll long long
#define P 1000000007
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int k,n,m,a,b,c;
int num[1<<15][2],num2[1<<15][2];
int getpos(int x){
int res=num2[x>>15][1];
x^=num2[x>>15][0];
return num[x][0]<=num[x][1]?num[x][0]+res:num[x][0]-res;
}
ll query(int l,int r){
if(!(l&1))return (((r-l+1)/2)&1?(n-1):0)^((r&1)?0:getpos(r));
return 1ll*((r-l)/2)*(n-1)+getpos(l)+((r&1)?0:getpos(r));
}
int main(){
k=read(),m=read(),n=(1<<k);
num[0][1]=(1<<k)-1;
for(int i=0;i<=min(k-1,14);i++)
for(int j=0;j<(1<<i);j++){
num[(1<<i+1)-j-1][0]=num[j][1];
num[(1<<i+1)-j-1][1]=num[j][1]-(num[j][1]-num[j][0])/2;
num[j][1]=num[j][0]+(num[j][1]-num[j][0])/2;
}
for(int i=0;i<(1<<max(k-15,0));i++)
for(int j=k-1,p=i<<15;j>=15;j--)
if(p>=(1<<j)){
p^=(1<<j+1)-1;
num2[i][0]^=(1<<j+1)-1;
num2[i][1]^=(1<<k-j)-1;
}
int l=read(),r=read(),h=0;
a=read(),b=read(),c=read();
for(int i=1;i<=m;i++){
h=(((1ll*query(l,r))^l^r^h)+c)%P;
l=(l^a^h)%(n+1)%n;
r=(r^b^h)%(n-l)+l;
}
printf("%d\n",h);
return 0;
}
B
签到题。
求
对 \(998244353\) 取模的值。
\(1\le n\le 10^9\),\(0\le k\le 1000\).
用多重二项式定理展开式子:
枚举 \(x,y\) 做等比数列求和。里面的式子为 \(1\) 时特判即可。
时间复杂度 \(O(k^2\log n)\).
怎么看起来处理 \(1\) 的时候我的好像假了。但是的确时过了的。
#include<bits/stdc++.h>
#define N 1010
#define P 998244353
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int qpow(int k,int b){
int ret=1;
while(b){
if(b&1)ret=1ll*ret*k%P;
k=1ll*k*k%P,b>>=1;
}
return ret;
}
int n,k,a,b,c;
int fac[N],ifac[N],pa[N],pb[N],pc[N];
int main(){
n=read(),k=read(),a=read(),b=read(),c=read();
fac[0]=ifac[0]=pa[0]=pb[0]=pc[0]=1;
for(int i=1;i<=k;i++){
fac[i]=1ll*fac[i-1]*i%P;
pa[i]=1ll*pa[i-1]*a%P,pb[i]=1ll*pb[i-1]*b%P,pc[i]=1ll*pc[i-1]*c%P;
}
ifac[k]=qpow(fac[k],P-2);
for(int i=k-1;i;i--)
ifac[i]=1ll*ifac[i+1]*(i+1)%P;
int ans=0,cur,tp;
for(int x=0;x<=k;x++)
for(int y=0,z;x+y<=k;y++){
z=k-x-y;
cur=1ll*ifac[x]*ifac[y]%P*ifac[z]%P;
tp=1ll*pa[x]*pb[y]%P*pc[z]%P;
int check=(a==1?x:0)+(b==1?y:0)+(c==1?z:0);
if(check==k)cur=1ll*cur*(n+1)%P;
else cur=1ll*cur*(P+qpow(tp,n+1)-1)%P*qpow((P+tp-1)%P,P-2)%P;
(ans+=cur)%=P;
}
printf("%d\n",1ll*ans*fac[k]%P);
return 0;
}
C
将数列 \(\{a_n\}\) 划分为若干段,使得相邻两端的最小值的差的绝对值 \(\ge E\).
问总方案数对 \(\rm 1e9+7\) 取模的值。
\(n\le 5\times 10^5\),\(1\le a_i,E\le 10^9\).
记 \(f_i\) 为 \(i\) 前面分好,\(i\) 为当前段的最小值的答案(该段可以未闭合)。
记 \(L_i\) 为比 \(i\) 小的上一个数。考虑转移。
上一段的最小值可能为 \([L[i],u)\),记为 \(j\).
\(f_j\) 无论切在 \([j+1,i]\) 的哪里都可以,即 \(f_j\) 对 \(i\) 的贡献为 \(f_j\times(k-j)\),其中 \(k\) 为下一个比 \(j\) 小的数。
那么 \(f_j\) 可以对 \([j,k)\) 作贡献,单调队列并维护版本和即可。
时间复杂度 \(O(n\log n)\).
#include<bits/stdc++.h>
#define N 500010
#define P 1000000007
#define V (int)1e9
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int rt[N],tot;
struct Tree{
int ls,rs,c0,c1;
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define c0(x) t[x].c0
#define c1(x) t[x].c1
}t[N<<6];
void modify(int &p,int l,int r,int x,int c0,int c1){
t[++tot]=t[p],p=tot;
(c0(p)+=c0)%=P,(c1(p)+=c1)%=P;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)modify(ls(p),l,mid,x,c0,c1);
else modify(rs(p),mid+1,r,x,c0,c1);
}
int query(int p,int l,int r,int L,int R,int opt){
if(!p)return 0;
if(L<=l&&r<=R)return !opt?c0(p):c1(p);
int mid=(l+r)>>1,ret=0;
if(L<=mid)ret=query(ls(p),l,mid,L,R,opt);
if(R>mid)(ret+=query(rs(p),mid+1,r,L,R,opt))%=P;
return ret;
}
int n,a[N],E;
int st[N],top,L[N];
int f[N];
int main(){
n=read(),E=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++){
while(top&&a[i]<=a[st[top]])top--;
L[i]=st[top],st[++top]=i;
}
top=0;
for(int u=1;u<=n;u++){
if(!L[u])f[u]=1;
int i=u,c0,c1,sum;
c0=(c0(rt[i-1])-query(rt[i-1],1,V,max(a[u]-E+1,1),min(a[u]+E-1,V),0)+P)%P;
c1=(c1(rt[i-1])-query(rt[i-1],1,V,max(a[u]-E+1,1),min(a[u]+E-1,V),1)+P)%P;
sum=(1ll*i*c0%P-c1+P)%P,(f[u]+=sum)%=P;
i=L[u];
c0=(c0(rt[i-1])-query(rt[i-1],1,V,max(a[u]-E+1,1),min(a[u]+E-1,V),0)+P)%P;
c1=(c1(rt[i-1])-query(rt[i-1],1,V,max(a[u]-E+1,1),min(a[u]+E-1,V),1)+P)%P;
sum=(1ll*i*c0%P-c1+P)%P,(f[u]+=P-sum)%=P;
rt[u]=rt[u-1];
while(top&&a[u]<=a[st[top]]){
modify(rt[u],1,V,a[st[top]],P-f[st[top]],1ll*(P-f[st[top]])*u%P);
top--;
}
st[++top]=u;
modify(rt[u],1,V,a[u],f[u],1ll*f[u]*u%P);
}
int ans=0,mn=V+1;
for(int i=n;i;i--)
if(mn>a[i])(ans+=f[i])%=P,mn=a[i];
printf("%d\n",ans);
return 0;
}
D
给出 \(m\) 条网线,对于每条网线有一个数 \(k_i\) 和 \(k_i\) 个 \([1,n]\) 中的数,表示该网线可以选取其中的两个子机相连(也可以不操作)。
一台子机至多连接一条网线,问最多使用的网线条数。
\(n\le 8000\),\(m\le 4400\),\(1\le k_i\le \min(40,n)\).
数据随机。
令每个子机对应一个点,每条网线对应两个点 \(x,y\),在两点间连一条边,\(x\) 和 \(y\) 均向能连接的电脑连边。
那么应有答案=匹配数-网线数。跑一般图最大匹配即可。
在火车头加持下常数很小。
#include<bits/stdc++.h>
#define N 16810
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,ans;
int node;
vector<int>e[N];
void add(int u,int v){
e[u].push_back(v),e[v].push_back(u);
}
int f[N],match[N];
int tag[N],tim,col[N],pre[N];
queue<int>q;
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
int lca(int x,int y){
tim++;
while(true){
if(x){
x=find(x);
if(tag[x]==tim)return x;
tag[x]=tim,x=pre[match[x]];
}
swap(x,y);
}
return -1;
}
void blossom(int x,int y,int w){
while(find(x)!=w){
pre[x]=y,y=match[x];
col[y]=1,q.push(y);
if(find(x)==x)f[x]=w;
if(find(y)==y)f[y]=w;
x=pre[y];
}
}
bool bfs(int s){
if((ans+1)*2>node)return false;
for(int i=1;i<=node;i++)f[i]=i,col[i]=pre[i]=0;
while(!q.empty())q.pop();
q.push(s),col[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int v:e[u]){
if(find(u)==find(v)||col[v]==2)continue;
if(!col[v]){
col[v]=2,pre[v]=u;
if(!match[v]){
int x=v,y,z;
while(x){
y=pre[x],z=match[y];
match[x]=y,match[y]=x;
x=z;
}
return true;
}
col[match[v]]=1,q.push(match[v]);
}
else{
int w=lca(u,v);
blossom(u,v,w),blossom(v,u,w);
}
}
}
return false;
}
int main(){
n=read(),m=read();
node=m*2+n;
for(int i=1,k,u;i<=m;i++){
k=read(),add(i,m+i);
while(k--)
u=read(),add(i,m*2+u),add(m+i,m*2+u);
}
for(int i=1;i<=node;i++)
if(!match[i])ans+=bfs(i);
printf("%d\n",ans-m);
return 0;
}