2019年7月博客汇总中
[SDOI2011]计算器
原题链接
一题更比三题强
第一问:快速幂
第二问:求逆元
第三问:BSGS
被这题的无解坑了,因为y,z不一定小于p,所以可能是p的倍数,要判y%p==0的情况
Code
#include<cstdio>
#include<cmath>
using namespace std;
namespace orz{
const int M=800000;
const int MOD=(1<<16)-1;
long long y,z,p;
int head[MOD+233],next[M],val[M],pos[M],tot;
inline int read(){
int b=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return b;
}
inline void add(int x,int j){
next[++tot]=head[x%MOD+1],head[x%MOD+1]=tot,val[tot]=x,pos[tot]=j;
}
inline int find(int x){
for(int i=head[x%MOD+1];i;i=next[i])
if(val[i]==x)
return pos[i];
return -1;
}
inline long long pow(long long x,long long y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%p;
x=x*x%p;
y>>=1;
}
return ans%p;
}
int QAQ(){
int t,k;
long long res;
long long m;
t=read();k=read();
while(t--){
y=read(),z=read(),p=read();
switch(k){
case 1:
printf("%lld\n",pow(y,z));
break;
case 2:
y=y%p;
z=z%p;
if(y==0&&z!=0){
printf("Orz, I cannot find x!\n");
break;
}
if(z==0){
printf("0\n");
break;
}
res=pow(y,p-2);
res=res*z%p;
printf("%lld\n",res);
break;
case 3:
y=y%p;
z=z%p;
if(y==0){
printf("Orz, I cannot find x!\n");
break;
}
m=sqrt(p);
for(int i=1;i<=MOD;++i)
head[i]=0;
tot=0;
res=-1;
for(int j=m-1;j>=0;--j){
res=pow(y,j);
res=pow(res,p-2);
res=res*z%p;
add(res,j);
}
for(int i=0;i*m<p;++i){
res=pow(y,i*m);
res=find(res);
if(~res){
res+=i*m;
break;
}
}
if(~res)printf("%lld\n",res);
else printf("Orz, I cannot find x!\n");
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
线性基学习笔记
科学定义的线性基
线性基是一个向量集合的极大线性无关组,满足集合内的所有向量可以由线性基中的向量线性表出。
线性基具有一些性质
1.线性基内的向量数目小于等于向量的维数
2.向量集合内所有的向量可以由线性基内的向量线性表出
3.不存在线性基的子集能够线性表出零向量
4.线性基可能有多个,但最终张出的向量空间是一定的,线性基中向量的个数是一定的
5.线性基内的向量是线性无关的
我们可以形象的认为线性基是单位向量的集合,比如\(\vec{x}=(0,1),\vec{y}=(1,0)\)那么我们就可以用\(a\vec{x}+b\vec{y}\)表示所有的平面向量,只要\(\vec{x},\vec{y}\)不共线,显然都是可以的。对于更高维的向量也是如此。
这种意义的线性基的题目比较少,求法可以用高斯消元。
OI中常用的线性基
OI中的线性基主要是用来解决异或和问题的。
定义:一个数集,这个数集中的数可以异或出所有原数集中的数(当然也可以异或出所有原数集能异或出的数)。
它有如下性质
1.线性基内数的数目小于等于最大数二进制位数
2.原数集中所有的数都可以由线性基中的数异或出
3.不存在线性基的子集可以异或出一
4.线性基可能有多个,但最后能表示的数集是一定的,线性基中数的个数是一定的
5.线性基内的数无法相互异或出
我们发现虽然由向量集变成了数集,但是两种线性基的性质十分相似。
我们将一个数二进制拆分后它就变成了一个模2意义下的向量,所以满足原来线性基的性质。
上述关系给了我们第一个线性基的求法--高斯消元,但是对于这种线性基我们还有更为巧妙的求法(好像思路就是高斯消元)。
这种方法的复杂度更优秀
这些做法所求出的线性基并不一定是原向量集/原数集中的元素
Code
inline bool add(long long x){
for(int i=63;i>=0;--i)
if(x&(1ll<<i)){
if(!d[i]){d[i]=x;return true;}
else x^=d[i];
}
return false;
}
这种做法的原理是每一次用之前求出的线性基将x的这一位消掉,相当与一个高斯消元的过程。最后求出来的d数组中,d[i]的最高位为1。
写线性基的时候特别要注意一点,如果数字是longlong范围的,一定要写成1ll<<i
我已经死了很多次了
[BZOJ3569]DZY Loves Chinese II
题目放在线性基里了肯定是线性基。
如果这题不强制在线的话可以线段树分治,但是这题强制在线。
我原来以为可以用线性基将强制在线处理掉,后来发现我错了。
看完题解后发现这个题不是人能想出来的。
题目最后的一句话:
Tip:请学会使用搜索引擎
所以我想不出来就去搜了Orz。
做法:
给原图随便取一棵生成树,对于每一个非树边取一个\(2^{64}-1\)范围内的随机权值,树边的权值为覆盖这条树边的非树边的权值的异或和,这里的覆盖指这条边的两端点分别位于去掉这条边后树的两个联通块。
去掉一些边后原图不连通当且仅当权值线性相关。线性相关可以用线性基判一下。
这个做法超大概率正确。
看到做法后我自闭了。
下面来证明这样做的正确性。
如果一个图不连通了,那么至少断掉了一条树边。
断掉一条树边后想要保证不联通需要将所有覆盖它的便都断掉。
而将所有覆盖它的边断掉必导致线性相关,因为树边的权值就是所有覆盖它的边异或起来。
所以这道题就在能过的时间复杂度内做完了。
因为这种做法的正确性很高,所以用int范围的随机数也能过。
Code
#include<cstdio>
#include<cstdlib>
using namespace std;
namespace orz{
const int N=200000,M=2000000;
struct Edge{
int x,y;
}e[M];
long long seed;
int head[N],ver[M],next[M],id[M],tot;
long long val[M],sum[N],c[M],d[80];
int a[N],k;
int sumans=0;
int vis[N];
bool inTree[M];
inline int read(){
int b=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return b;
}
inline long long rd(){
return seed=seed*19261817+17+rand();
}
inline void add(int x,int y,int di){
e[di].x=x,e[di].y=y;
next[++tot]=head[x],head[x]=tot,ver[tot]=y,id[tot]=di;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,id[tot]=di;
}
void dky(int x){
int y;
vis[x]=true;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(vis[y])continue;
inTree[id[i]]=true;
dky(ver[i]);
}
}
void dfs(int x,int edgeid,int father){
sum[x]=c[x];
for(int i=head[x];i;i=next[i]){
if(!inTree[id[i]])continue;
if(ver[i]==father)continue;
dfs(ver[i],id[i],x);
sum[x]^=sum[ver[i]];
}
val[edgeid]=sum[x];
}
inline bool add(long long x){
for(int i=63;i>=0;--i)
if(x&(1ll<<i)){
if(!d[i]){d[i]=x;return true;}
else x^=d[i];
}
return false;
}
inline bool check(){
for(int i=1;i<=k;++i)
a[i]^=sumans;
for(int i=0;i<=64;++i)
d[i]=0;
for(int i=1;i<=k;++i)
if(!add(val[a[i]]))
return false;
return true;
}
int QAQ(){
read();
int m=read();
seed=19260817;
srand(seed);
for(int i=1;i<=m;++i)
add(read(),read(),i);
dky(1);
for(int i=1;i<=m;++i){
if(!inTree[i]){
val[i]=rd();
c[e[i].x]^=val[i];
c[e[i].y]^=val[i];
}
}
dfs(1,0,0);
int q=read();
while(q--){
k=read();
for(int i=1;i<=k;++i)
a[i]=read();
if(check())printf("Connected\n"),++sumans;
else printf("Disconnected\n");
}
return false;
}
}
int main(){
return orz::QAQ();
}
[BZOJ4184]shallot
线性基肯定是满足可并的,所以我一眼就想到了Splay维护线性基,时间复杂度大概是\(O(32^2nlogn)\)带个大常数的,目测过不了。
7.22爆零赛
爆零了QAQ,我的数论太菜了。
考场上忘了exgcd现推了一遍,后来发现好像是对的。
Exgcd复习
方程的解
题目大意:求\(ax+by=c\)的正整数解的个数。
看式子就是exgcd的样子,实际上就是exgcd。
但是我忘了exgcd,所以现推了一遍exgcd,然后害怕出问题打了暴力走人了。
然后尴尬的是没有把暴力的特判打全。
Code
内含大力特判
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
void exgcd(long long a,long long b,long long &x,long long &y){
if(b){exgcd(b,a%b,y,x),y-=(a/b)*x;}
else{x=1,y=0;}
}
long long gcd(long long a,long long b){
return b?gcd(b,a%b):a;
}
inline bool WA(){
printf("ZenMeZheMeDuo\n");
return true;
}
inline bool AC(long long x){
if(x<0)printf("0\n");
else if(x>65535)WA();
else printf("%lld\n",x);
return true;
}
inline bool check(long long a,long long b,long long c){
if(a+b==c)return AC(1);
if(a==0&&b==0&&c==0)return WA();
if(a==1&&b==1)return AC(max(0ll,c-1));
if(a>0&&b>0&&c<=0)return AC(0);
if(a<0&&b<0&&c>=0)return AC(0);
if(a==0){
if(b==0)return AC(0);
if(b*c<0)return AC(0);
else if(c%b==0)return WA();
else return AC(0);
}
swap(a,b);
if(a==0){
if(b==0)return AC(0);
if(b*c<0)return AC(0);
else if(c%b==0)return WA();
else return AC(0);
}
return false;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
long long x,y;
long long a,b,c;
long long res;
int t=read();
while(t--){
a=read(),b=read(),c=read();
if(c<0)a=-a,b=-b,c=-c;
if(check(a,b,c))continue;
if(a>0&&b>0&&c>0){
res=gcd(a,b);
if(c%res){AC(0);continue;}
exgcd(a,b,x,y);
x*=c/res;
y*=c/res;
x=(x%(b/res)+b/res)%(b/res);
if(x==0)x+=b/res;
y=(y%(a/res)+a/res)%(a/res);
if(y==0)y+=a/res;
AC(((c-a*x)/b-y)/(a/res)+1);
continue;
}
else {
if(a<0)a=-a;
else b=-b;
res=gcd(a,b);
if(c%res)AC(0);
else WA();
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
visit
题目大意:上下左右走t步(可以走出去)从(0,0)走到(n,m)的方案数。
一看是个组合问题,可以把横向走和纵向走分开考虑。
首先走一步必改变奇偶性,所以可以用奇偶性判一下无解,不过好像并没有放分。
我们考虑横着走和纵着走分开算,如果横着走的总步数一定,那么向右和向左的步数也是固定的。
所以组一下就可以。
\(ans=\sum\limits_{i=n}^{t-m}(^t_i)\(^i_{\frac{i+n}{2}})(^{t-i}_{\frac{t-i+m}{2}})\)(其中i每次加2)
重点在取模。
正解是将模数分解最后用excrt合并。
我是将所有阶乘中的模数所具有的质因子提出,分开计算。
后来对拍发现我的做法并没有错的说,而且跑的也不慢qaq。
最后发现n,m还有负数,改了就A了。
Code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace orz{
const int N=410000;
long long mod;
int prime[20];
long long fac[N],ni[N];
int sum[13][N];
int cnt[N],tot;
long long t,n,m;
inline void exgcd(long long &x,long long &y,long long a,long long b){
if(b){exgcd(y,x,b,a%b);y-=(a/b)*x;}
else{x=1,y=0;}
}
inline void premod(){
int t=sqrt(mod);
long long res=mod;
for(int i=2;i<=t;++i)
if(res%i==0){
prime[++tot]=i;
res/=i;
}
if(res>1)prime[++tot]=res;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline long long pow(long long x,int y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans%mod;
}
inline int C(int x,int y){
return fac[x]*ni[x-y]%mod*ni[y]%mod;
}
inline void CC(int x,int y){
for(int i=1;i<=tot;++i){
cnt[i]+=sum[i][x];
cnt[i]-=sum[i][y];
cnt[i]-=sum[i][x-y];
}
}
inline int calc(long long x){
long long res=1;
res=res*C(t,x)%mod;
res=res*C(x,(x+n)/2)%mod;
res=res*C(t-x,(t-x+m)/2)%mod;
CC(t,x);
CC(x,(x+n)/2);
CC(t-x,(t-x+m)/2);
for(int i=1;i<=tot;++i){
res=res*pow(prime[i],cnt[i])%mod;
cnt[i]=0;
}
return res;
}
inline void prefac(){
fac[0]=fac[1]=1;
int res=0;
long long x,y;
for(int i=2;i<=t;++i){
for(int j=1;j<=tot;++j)
sum[j][i]=sum[j][i-1];
res=i;
for(int j=1;j<=tot;++j)
while(res%prime[j]==0)
res/=prime[j],++sum[j][i];
fac[i]=fac[i-1]*res%mod;
}
ni[1]=ni[0]=1;
for(int i=2;i<=t;++i){
exgcd(x,y,fac[i],mod);
ni[i]=(x+mod)%mod;
}
}
int QAQ(){
t=read(),mod=read();
n=read(),m=read();
if(t<0)t=-t;
if(n<0)n=-n;
if(m<0)m=-m;
premod();
prefac();
long long ans=0;
if(((n+m)&1)!=(t&1)||t<n+m){
printf("0\n");
return false;
}
for(int i=n;i+m<=t;i+=2){
ans=(ans+calc(i))%mod;
}
printf("%lld\n",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
CRT版
思路就是每一个质因子求出一个答案,最后用CRT合并。
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace orz{
const int N=200000;
long long t,mod,n,m;
int prime[30],tot;
long long fac[N],inv[N];
long long ans[30];
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
long long pow(long long x,long long y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%mod;
y>>=1;
x=x*x%mod;
}
return ans;
}
long long C(int n,int m){
if(n<m)return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
long long lucas(long long n,long long m){
if(!m)return 1;
return lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod;
}
void fj(int x){
int t=sqrt(x);
for(int i=2;i<=t;++i)
if(x%i==0)
x/=i,prime[++tot]=i;
if(x>1)prime[++tot]=x;
}
int QAQ(){
long long M;
t=read(),M=mod=read(),n=read(),m=read();
fj(mod);
if(t<0)t=-t;
if(n<0)n=-n;
if(m<0)m=-m;
if((t&1)!=((n+m)&1)||t<(n+m)){
printf("0\n");
return false;
}
long long Ans=0;
int tt;
long long res;
long long mm;
for(int p=1;p<=tot;++p){
mod=prime[p];
fac[0]=1;
tt=min(mod-1,t);
for(int i=1;i<=tt;++i)
fac[i]=fac[i-1]*i%mod;
inv[tt]=pow(fac[tt],mod-2);
for(int i=tt-1;i>=0;--i)
inv[i]=inv[i+1]*(i+1)%mod;
for(int i=n;i+m<=t;i+=2)
ans[p]=(ans[p]+lucas(t,i)*lucas(i,(i+n)/2)%mod*lucas(t-i,(t-i+m)/2)%mod)%mod;
}
for(int i=1;i<=tot;++i){
mm=M/prime[i];
mod=prime[i];
res=pow(mm,prime[i]-2);
Ans=(Ans+res*mm%M*ans[i]%M)%M;
}
printf("%lld",Ans%M);
return false;
}
}
int main(){
return orz::QAQ();
}
光
暴力模拟能得60分,但我写完T2已经没时间了。
正解就是暴力模拟的优化,我们发现直的行走都不会改变x+y的奇偶性,而每一次90度拐弯必改变坐标奇偶性,这导致一个方块只有一种被经过的方向,所以我们斜向建vector,每次二分查找下一个位置,由于不会重复经过,直接把路径长度加上就可以了。
最后有成环和折反两种情况,我们可以把初始块打上标记,进行一些判断就可以了。
这题比赛的数据有点弱,我有一些情况没有考虑到,CF的数据是真的强。
我忘了考虑的是把起始块当作障碍后形成三角挡住光路的情况。
Code
极致压行
#include<bits/stdc++.h>
using namespace std;
namespace orz{
void run(int,int,int);
void turn(int,int,int);
const int N=300000;
const int ddx[4]={1,0,-1,0},ddy[4]={0,1,0,-1};
const int dx[4]={-1,1,1,-1},dy[4]={1,1,-1,-1};
const int NE=0,SE=1,SW=2,NW=3;
const int left[4]={2,1,0,3},right[4]={1,0,3,2};
long long ans=0;
bool flag=false;
int n,m,nm,sx,sy,sd,t,tx,ty;
bool l,r;
vector<int>a[N],b[N];
inline bool findBlock(int x,int y){return *lower_bound(a[x+y].begin(),a[x+y].end(),x)==x;}
inline void add(int x,int y){a[x+y].push_back(x);b[x-y+nm].push_back(x);}
void turn(int x,int y,int direct){
if(x+dx[direct]==sx&&y+dy[direct]==sy){flag=true;return;}
l=findBlock(x+ddx[left[direct]],y+ddy[left[direct]]);
r=findBlock(x+ddx[right[direct]],y+ddy[right[direct]]);
if((l&&r)||(!l&&!r)){
if(x+ddx[left[direct]]==sx&&y+ddy[left[direct]]==sy)flag=true;
if(x+ddx[right[direct]]==sx&&y+ddy[right[direct]]==sy)flag=true;
return;
}
if(!l){
tx=x+ddx[left[direct]],ty=y+ddy[left[direct]];
if(tx==sx&&ty==sy)flag=true;
else run(tx,ty,(direct-1+4)%4);
}
else{
tx=x+ddx[right[direct]],ty=y+ddy[right[direct]];
if(tx==sx&&ty==sy)flag=true;
else run(tx,ty,(direct+1)%4);
}
}
void run(int x,int y,int direct){
switch(direct){
case NE:
t=lower_bound(a[x+y].begin(),a[x+y].end(),x)-a[x+y].begin();
tx=a[x+y][t-1]+1;ty=x+y-tx;
ans+=(long long)x-tx+1;
turn(tx,ty,direct);
break;
case SE:
t=upper_bound(b[x-y+nm].begin(),b[x-y+nm].end(),x)-b[x-y+nm].begin();
tx=b[x-y+nm][t]-1;ty=tx-x+y;
ans+=(long long)tx-x+1;
turn(tx,ty,direct);
break;
case SW:
t=upper_bound(a[x+y].begin(),a[x+y].end(),x)-a[x+y].begin();
tx=a[x+y][t]-1;ty=x+y-tx;
ans+=(long long)tx-x+1;
turn(tx,ty,direct);
break;
case NW:
t=lower_bound(b[x-y+nm].begin(),b[x-y+nm].end(),x)-b[x-y+nm].begin();
tx=b[x-y+nm][t-1]+1;ty=tx-x+y;
ans+=(long long)x-tx+1;
turn(tx,ty,direct);
break;
}
}
long long dfs(){
run(sx,sy,sd);
if(flag)return ans;
run(sx,sy,(sd+2)%4);
return ans-1;
}
int QAQ(){
int n,m,k,x,y;
char di[4];
scanf("%d%d%d",&n,&m,&k);
nm=m+2333;
for(int i=0;i<=n+1;++i)add(i,0),add(i,m+1);
for(int i=1;i<=m;++i)add(0,i),add(n+1,i);
for(int i=1;i<=k;++i)scanf("%d%d",&x,&y),add(x,y);
scanf("%d%d%s",&sx,&sy,di);add(sx,sy);
if(di[0]=='N'){if(di[1]=='E')sd=0;else sd=3;}
else{if(di[1]=='E')sd=1;else sd=2;}
for(int i=0;i<=n+m+2;++i)sort(a[i].begin(),a[i].end());
for(int i=-m-3;i<=n+3;++i)sort(b[i+nm].begin(),b[i+nm].end());
printf("%lld\n",dfs());
return false;
}
}
int main(){
return orz::QAQ();
}
赛后感想
能打的暴力一定要打上QwQ
Exgcd学习笔记
叫学习笔记只是为了格式统一,其实是复习,因为考试忘了exgcd挂的能惨。
exgcd可以求解\(ax+by=gcd(a,b)\)
这个东西一定有解,那个好像叫裴蜀定理。这里不再证明。
具体证明:
我们都会GCD。
\(gcd(a,b)=gcd(b,a\mod b)\)
与上面形式相同,下面方程一定有解。
\(bx+(a \mod b)y=gcd(b,a \mod b)\)
\(bx+(a- \frac{a}{b}\cdot b)y=gcd(b,a \mod b)\)
注意下面的x和y与上面是不一样的,指的是新的解。
我们将a,b转化为原来的格式。
\(bx+ay-\frac{a}{b}\cdot by=gcd(b,a\mod b)\)
\(ay+(x-\frac{a}{b}\cdot y)b=gcd(b,a\mod b)\)
我们看下面两个式子,我们可以用更小范围的解来求出更大范围的解。
\(ay+(x-\frac{a}{b}\cdot y)b=gcd(a,b)\)
\(ax+by=gcd(a,b)\)
所以我们可以递归求解。
Code
极致的压行体验
void exgcd(long long a,long long b,long long &x,long long &y){
if(b){exgcd(b,a%b,y,x);y-=(a/b*x);}
else{x=1;y=0;}
}
一次爆零考试考场上推出来的不压行版(交了模板后发现是对的)
inline void exgcd(long long &x,long long &y,long long a,long long b){
if(!b){
x=1,y=0;
return ;
}
exgcd(x,y,b,a%b);
long long tmp=y;
y=x-(a/b)*y;
x=tmp;
return ;
}
上面两个代码中最后的y是什么并不影响最后答案。
因为最后方程为\(1x+0y=1\)
Exgcd求逆元
Exgcd可以求逆元,在大多数情况下应该比费马小定理快,而且适用于模数不是质数的情况。
求\(ax≡1mod P\)等价于求\(ax+Py=1\)的正整数解x
直接exgcd就可以。
void exgcd(long long a,long long b,long long &x,long long &y){
if(b){exgcd(b,a%b,y,x),y-=(a/b)*x;}
else{x=1,y=0;}
}
int main(){
long long a,p;
a=read(),p=read();
long long x,y;
exgcd(a,p,x,y);
x=(x+p)%p;
printf("%lldn",x);
}
Exgcd求方程解
\(ax+by=c\)
如果\(c\mod gcd(x,y)!=0\)无解
先exgcd后求得的x,y是\(ax+by=gcd(a,b)\)的解,所以应该乘上\(\frac{c}{gcd(a,b)}\)
之后通解一样。
CRT/EXCRT学习笔记
虽然听过很多遍CRT,但感觉自己好像没有懂过。
先说EXCRT,因为我没有看懂CRT,而且这两个东西好像除了名字没有什么相似的。
EXCRT
它是用来解同余方程组的。
就像这样
\(\begin{cases}
x≡b_1\pmod {a_1} \\
x≡b_2\pmod {a_2}\\
... \\
x≡b_n\pmod {a_n}
\end{cases}
\)
拿我们怎么解呢,我们考虑两个两个方程合并。比如我们要合并前两个方程。我们可以转化为以下式子。
\(x=a_1*k_1+b_1\)
\(x=a_2*k_2+b_2\)
\(a_1k_1+b_1=a_2k_2+b_2\)
\(a_1k_1-a_2k_2=b_2-b_1\)
那我们就可以用exgcd来求解了。
合并之后我们得到了一个最小非负整数解x。
而且得到了方程:
\(x≡ans_{1,2}\pmod {LCM(a_1,a_2)}\)
我们新得到的方程也是和原来的方程形式一致的,所以我们可以逐个合并,最终得到答案。
Code
inline long long mul(long long x,long long y,long long mod){
long long ans=0;
while(y){
if(y&1)ans=(ans+x)%mod;
y>>=1;
x=(x+x)%mod;
}
return ans;
}
inline long long exgcd(long long &x,long long &y,long long a,long long b){
if(b){long long d=exgcd(y,x,b,a%b);y-=(a/b)*x;return d;}
else{x=1,y=0;return a;}
}
int QAQ(){
int n=read();
long long c,d,res,x,y;
for(int i=1;i<=n;++i)
a[i]=read(),b[i]=read();
for(int i=2;i<=n;++i){
//c是方程右边的数
c=((b[i]-b[i-1])%a[i]+a[i])%a[i];
//d=gcd(a[i],a[i-1]);
d=exgcd(x,y,a[i-1],a[i]);
//x的通解是x+t*a[i]/d
res=a[i]/d;
//求出最小解x
x=mul(x,c/d,res);
//x带回式子更新答案
b[i]=x*a[i-1]+b[i-1];
//更新a=LCM(a[i-1],a[i])
a[i]=res*a[i-1];
//取最小非负整数解
b[i]=(b[i]%a[i]+a[i])%a[i];
}
printf("%lld",b[n]);
return false;
}
CRT
CRT为什么少了EX呢,因为它只能处理\(a_i\)互质的情况,所以它完全可以被EXCRT替代。
\(\begin{cases}
x≡b_1\pmod {a_1} \\
x≡b_2\pmod {a_2}\\
... \\
x≡b_n\pmod {a_n}
\end{cases}
\)
它还是用来求解上述同余方程组的,只是要求\(a_i\)两两互质。
设\(M=\prod\limits_{i=1}^na_i\)
设\(M_i=\frac{M}{a_i}\)
构造n个数是,其中第\(i\)个数是\(M_i\)的倍数且\(\mod a_i=1\)
即:
\(M_ix_i≡1\pmod {a_i}\)
我们求逆元就可以了。
设\(c_i=M_ix_i\)
最后:
\(ans=\sum\limits_{i=1}^nc_ib_i\mod M\)
为什么呢?
因为\(c_i≡1\pmod {a_i}\)
所以\(c_ib_i≡b_i\pmod {a_i}\)
因为\(c_i\)是其他a的倍数,所以加了不影响答案。
龟速乘学习笔记
我们有的时候会遇到最终的数不爆longlong,而中间乘的过程会爆longlong的情况,这时候我们需要用到龟速乘(为什么不用神奇的__int128呢)。
原理是将一个数二进制分解,写起来和快速幂差不多。
Code
long long slowMul(long long a,long long b){
long long ans=0;
while(b){
if(b&1)ans=(ans+a)%mod;
b>>=1;
a=(a+a)%mod;
}
return ans;
}
Lucas/Exlucas学习笔记
最近做一些组合数需要取模,对于一些模数不是素数的情况可以用Exlucas,对于一些模数较小的可以用Lucas.
Lucas
有的时候求组合数,它的大小大于了模数,这会导致阶乘中出现0,而因为0没有逆元会导致WrongAnswer。
式子:
\((^n_m)≡(^{n/p}_{m/p})(^{n\mod p}_{m\mod p})\pmod p\)
递归往下算就可以了。
Exlucas
[SDOI2013]随机数生成器
原题链接
不会数列233,没学过Orz。
题目大意给定\(x_1,a,b,p\)
\(x_{i+1}≡ax_i+b\pmod p\)
求使\(x_i=t\)的最小\(i\)。
那我们先求通项公式
\(x_{i+1}+y=z(x_i+y)\)
\(x_{i+1}=zx_i+zy-y\)
\(\begin{cases}
zy-y=b \\
z=a \\
\end{cases}\)
解得
\(\begin{cases}
y=\frac{b}{a-1} \\
z=a \\
\end{cases}\)
\(x_i=a^{i-1}(x_1+\frac{b}{a-1})-\frac{b}{a-1}\)
所以用BSGS求
\(t≡a^{i}(x_1+\frac{b}{a-1})-\frac{b}{a-1}\)
但是我们是按一个等比数列推的,而a可能为1或0,这个题有各种特判情况,不判就爆零了QAQ。
学某位DD的BSGS用im-j代码快了不少。
Code
#include<cstdio>
#include<cmath>
using namespace std;
namespace orz{
const int N=200000;
const int mod=499211;
const int inf=2147483647;
long long a,b,x,t,p;
long long head[mod+233],next[N],val[N],pos[N],tot;
inline int pow(long long x,long long y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%p;
x=x*x%p;
y>>=1;
}
return ans;
}
inline void add(int x,int j){
int res=x%mod+1;
next[++tot]=head[res],head[res]=tot,val[tot]=x,pos[tot]=j;
}
inline int find(int x){
for(int i=head[x%mod+1];i;i=next[i])
if(val[i]==x)
return pos[i];
return inf;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
int T;
long long res;
long long ans;
scanf("%d",&T);
while(T--){
scanf("%lld%lld%lld%lld%lld",&p,&a,&b,&x,&t);
if(x==t){
printf("1\n");
continue;
}
if(a==0){
if(x==t){
printf("1\n");
continue;
}
if(b==t){
printf("2\n");
continue;
}
printf("-1\n");
continue;
}
if(a==1){
if(b==0&&x!=t){
printf("-1\n");
continue;
}
long long tmp=pow(b,p-2);
printf("%lld\n",((t-x+p)%p*tmp%p+1));
continue;
}
for(int i=0;i<=mod;++i)
head[i]=0;
tot=0;
res=pow(a-1,p-2);
t=(t+b*res%p)%p;
res=(x+b*res%p)%p;
res=pow(res,p-2);
t=t*res%p;
//================
int m=ceil(sqrt(p));
res=1;
ans=inf;
for(int j=0;j<=m;++j,res=res*a%p)
add(res*t%p,-j);
long long tmp=pow(a,m);
res=tmp;
for(int i=1;i<=m;++i,res=res*tmp%p)
if((ans=find(res))!=inf){ans+=i*m;break;}
if(ans==inf)printf("-1\n");
else printf("%lld\n",((ans+1)%p+p)%p);
}
return false;
}
}
int main(){
return orz::QAQ();
}
[BZOJ4128]Matrix
原题链接
矩阵BSGS。
矩阵也是满足费马小定理的,所以我们可以用快速幂求逆元,然后你必TLE。
所以我们还是用DD的做法来优化复杂度,这样你的复杂度就很好。
这一题有些卡常,注意代码中的res初始值为b否则每一次进行一次矩阵乘法会TLE。
Code
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
namespace orz{
const int N=200;
const int mod=293,K=277;
const int inf=2147483647;
int n,p;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
struct Matrix{
int a[71][71];
Matrix(){memset(a,0,sizeof(a));}
Matrix operator*(const Matrix &b)const{
Matrix res;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=n;++k)
res.a[i][j]=(res.a[i][j]+a[i][k]*b.a[k][j])%p;
return res;
}
bool operator==(const Matrix &b)const{
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(a[i][j]!=b.a[i][j])
return false;
return true;
}
int getHash(){
int res=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)
res=(res+a[i][j])%mod;
res=(res*K)%mod;
}
return res;
}
};
int head[mod+233],next[N],pos[N],tot;
Matrix val[N];
inline void add(Matrix x,int j){
int tmp=x.getHash()%mod+1;
next[++tot]=head[tmp],head[tmp]=tot,val[tot]=x,pos[tot]=j;
}
inline int find(Matrix x){
for(int i=head[x.getHash()%mod+1];i;i=next[i])
if(val[i]==x)
return pos[i];
return inf;
}
inline Matrix pow(Matrix x,int y){
Matrix ans;
for(int i=1;i<=n;++i)
ans.a[i][i]=1;
while(y){
if(y&1)ans=ans*x;
x=x*x;
y>>=1;
}
return ans;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
Matrix a,b;
scanf("%d %d",&n,&p);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
a.a[i][j]=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
b.a[i][j]=read();
Matrix res,tmp;
res=b;
int m=ceil(sqrt(p));
for(int i=0;i<=m;++i,res=res*a)
add(res,-i);
tmp=pow(a,m);
res=tmp;
int ans=inf;
for(int i=1;i<=m;++i,res=res*tmp)
if((ans=find(res))!=inf){
ans+=i*m;
break;
}
if(ans^inf)printf("%d\n",ans);
return false;
}
}
int main(){
return orz::QAQ();
}
[BZOJ4358]Permu
原题链接
在莫队专题肯定是个莫队,我们要求维护最长连续段的大小。
我们套个线段树不就可以了?
实测线段树最大数据大概要跑7000ms,说不定卡卡常能过(时限3000ms)?
写线段树的时候出了一些问题,以后写莫队要注意指针的移动顺序,这一题乱动的话会WA,而先扩展再收缩不会,因为我们默认了每个数只有一个,所以只有按顺序来才不会出现负数。
因为TLE了一直在想其他做法,其实这道题线段树卡卡常是能过的,我把莫队优化中的&1打成了^1,所以就T了。
Code
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
namespace orz{
const int N=60000,M=60000;
int pos[N];
struct md{
int l,r,id;
bool operator<(const md&b)const{
return pos[this->l]^pos[b.l]?pos[this->l]<pos[b.l]:(pos[this->l]&1?this->r<b.r:this->r>b.r);
}
}mc[M];
struct Tree{
int ans,lans,rans;
}t[(N<<2)+233];
int a[N],ans[M],n,m,block;
inline void change(int x,int p,int k,int L,int R){
if(L==R){t[x].lans=t[x].rans=t[x].ans=k;return;}
int mid=(L+R)>>1;
if(p<=mid)change(x<<1,p,k,L,mid);
else change(x<<1|1,p,k,mid+1,R);
int lc=x<<1,rc=x<<1|1;
t[x].ans=max(t[lc].rans+t[rc].lans,max(t[lc].ans,t[rc].ans));
t[x].lans=(t[lc].ans==(mid-L+1)?t[lc].ans+t[rc].lans:t[lc].lans);
t[x].rans=(t[rc].ans==(R-mid)?t[rc].ans+t[lc].rans:t[rc].rans);
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int QAQ(){
n=read();m=read();
block=sqrt(m);
for(int i=1;i<=n;++i)
pos[i]=(i-1)/block+1;
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=m;++i)mc[i].l=read(),mc[i].r=read(),mc[i].id=i;
sort(mc+1,mc+m+1);
register int l=mc[1].l,r=mc[1].r;
for(int i=l;i<=r;++i)
change(1,a[i],1,1,n);
ans[mc[1].id]=t[1].ans;
for(int i=2;i<=m;++i){
while(l>mc[i].l)change(1,a[--l],1,1,n);
while(r<mc[i].r)change(1,a[++r],1,1,n);
while(l<mc[i].l)change(1,a[l++],0,1,n);
while(r>mc[i].r)change(1,a[r--],0,1,n);
ans[mc[i].id]=t[1].ans;
}
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return false;
}
}
int main(){
return orz::QAQ();
}
[HAOI2012]高速公路
这是个假期望,相当于求所有路径长度除以方案数。
我们考虑每一个点对与答案的贡献(一个点表示一段路)
\(ans=\sum\limits_{i=l}^r2s_i(i-l+1)(r-i+1)\)
把后面的式子拆开:
\(2(s_iir+s_iil+s_ir-s_il-s_ii^2-s_ilr+s_i)\)
在变的只有\(s_i,s_ii,s_ii^2\)分别维护就好。
#include<cstdio>
using namespace std;
namespace orz{
const int N=1100000;
#define lc x<<1
#define rc x<<1|1
inline long long read(){
long long a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline bool getOrder(){
char t;
do{t=getchar();}while(t!='C'&&t!='Q');
return t=='Q';
}
struct node{
long long s,si,sii,add,l,r;
}t[(N<<2)+233];
struct Ans{
long long s,si,sii;
Ans(){s=si=sii=0;}
Ans(long long _s,long long _si,long long _sii){s=_s;si=_si;sii=_sii;}
Ans operator+(const Ans&b)const{
Ans res;
res.s=s+b.s,res.si=si+b.si,res.sii=sii+b.sii;
return res;
}
};
inline void build(int x,int l,int r){
t[x].l=l,t[x].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
inline long long gcd(long long a,long long b){return b?gcd(b,a%b):a;}
inline long long calc(long long l,long long r,Ans x){return x.si*(r+l)-x.sii-x.s*l*r+x.s*(r-l)+x.s;}
inline long long calci(long long x){return x*(1+x)/2;}
inline long long calcii(long long x){return x*(x+1)*(2*x+1)/6;}
inline void pushs(int x,long long add){t[x].s+=add*(t[x].r-t[x].l+1);}
inline void pushsi(int x,long long add){t[x].si+=add*(calci(t[x].r)-calci(t[x].l-1));}
inline void pushsii(int x,long long add){t[x].sii+=add*(calcii(t[x].r)-calcii(t[x].l-1));}
inline void pushadd(int x,long long add){t[x].add+=add;}
inline void print(long long x,long long y){long long d=gcd(x,y);printf("%lld/%lld\n",x/d,y/d);}
inline void pushall(int x,long long add){pushs(x,add),pushsi(x,add),pushsii(x,add),pushadd(x,add);}
inline void pushdown(int x){
if(t[x].add){
pushall(lc,t[x].add);
pushall(rc,t[x].add);
t[x].add=0;
}
}
inline Ans query(int x,int l,int r){
if(t[x].l==l&&t[x].r==r)return Ans(t[x].s,t[x].si,t[x].sii);
pushdown(x);
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)return query(lc,l,r);
else if(l>mid) return query(rc,l,r);
else return query(lc,l,mid)+query(rc,mid+1,r);
}
inline void change(int x,int l,int r,long long k){
if(t[x].l==l&&t[x].r==r){pushall(x,k);return;}
pushdown(x);
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)change(lc,l,r,k);
else if(l>mid)change(rc,l,r,k);
else change(lc,l,mid,k),change(rc,mid+1,r,k);
t[x].s=t[lc].s+t[rc].s;t[x].si=t[lc].si+t[rc].si;t[x].sii=t[lc].sii+t[rc].sii;
}
int QAQ(){
int n=read(),m=read();
build(1,1,n-1);
long long l,r;
while(m--){
switch(getOrder()){
case true:
l=read(),r=read()-1;
print(calc(l,r,query(1,l,r)),(r-l+2)*(r-l+1)/2);
break;
case false:
l=read(),r=read()-1;
change(1,l,r,read());
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
7.25爆零赛
爆零了Orz
匹配
KMP,秒了
Code
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=300000;
int next[N];
char s[N];
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline char getc(){
char t;
do{t=getchar();}while(t<'a'||t>'z');
return t;
}
int QAQ(){
int t=read();
while(t--){
int alen=read(),blen=read();
int len=alen+blen+2;
char c;
scanf("%s",s+1);
for(int i=1;i<=blen;++i)
s[alen+1+i]=s[i];
c=getc();
s[len]=c;
for(int i=1;i<=len;++i)
next[i]=0;
for(int i=1;i<=len;++i){
int pos=next[i-1];
while(pos&&(s[pos+1]!=s[i]||pos+1==i))pos=next[pos];
if(s[pos+1]==s[i]&&pos+1!=i)next[i]=pos+1;
}
printf("%d\n",next[len]);
}
return false;
}
}
int main(){
return orz::QAQ();
}
回家
开始以为直接输出所有在最短路上的割点就可以,但是后来发现有一点偏差。
主要是如果一个点在一个点双中的话,这个点双中最短路上的割点不一定必须经过。
假算法还骗了40分,RP较好。
正解是圆方树,秒了。
Code
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
namespace orz{
const int N=600000,M=2400000;
bool vis[N];
bool ans[N];
int cnt;
int stack[N],top;
int pre[N];
int dfn[N],low[N],num;
int head[N],ver[M],next[M],tot;
int n,m;
vector<int>dcc[N];
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y){
next[++tot]=head[x],head[x]=tot,ver[tot]=y;
next[++tot]=head[y],head[y]=tot,ver[tot]=x;
}
inline int BFS(){
int x,y;
queue<int>q;
vis[1]=true;
q.push(1);
while(q.size()){
x=q.front();
q.pop();
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(vis[y])continue;
vis[y]=true;
pre[y]=x;
q.push(y);
if(y==n)break;
}
}
int sum=-2;
for(int i=n;i;i=pre[i]){
if(i<=n){
ans[i]=true;
++sum;
}
}
return sum;
}
void tarjan(int x){
dfn[x]=low[x]=++num;
stack[++top]=x;
int y;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(!dfn[y]){
tarjan(ver[i]);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){
int t;
++cnt;
dcc[cnt].clear();
do{
t=stack[top--];
dcc[cnt].push_back(t);
}while(t!=y);
dcc[cnt].push_back(x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
inline void clear(){
for(int i=1;i<=n;++i)
dfn[i]=low[i]=head[i]=0;
for(int i=1;i<=n;++i)
ans[i]=false;
tot=num=0;
cnt=0;
}
int QAQ(){
// freopen("qaq.in","r",stdin);
int t=read();
int x,y;
while(t--){
n=read(),m=read();
clear();
for(int i=1;i<=m;++i){
x=read(),y=read();
if(x==y)continue;
add(x,y);
}
tarjan(1);
for(int i=1;i<=n+cnt;++i)
head[i]=0;
tot=0;
for(int i=1;i<=cnt;++i)
for(vector<int>::iterator j=dcc[i].begin();j!=dcc[i].end();++j)
add(n+i,*j);
for(int i=1;i<=n+cnt;++i)
vis[i]=false,pre[i]=0;
printf("%d\n",BFS());
for(int i=2;i<n;++i)
if(ans[i])
printf("%d ",i);
putchar('\n');
}
return false;
}
}
int main(){
return orz::QAQ();
}
寿司
做的脑子疼QAQ。
首先断环拆成两倍是套路,这样环上问题就变成了序列上的问题。
我们对于其中长度为n的一段,对应了一种断环方式。
我们把问题转化为0和1.
对于每一种断环方式,相当于是将其中的一些1移到左边,另一些移到右边,我们枚举分割点便得到了\(O(n^2)\)的做法。
随着区间的右移我们的最优决策点也是单调右移。
所以我们就可以\(O(n)\)切掉这个题。
至于证明?
大胆猜想,不必证明
--黄学长
Code
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=3000000;
const long long inf=1ll<<62;
int a[N];
long long front[N],back[N];
long long frontc[N],backc[N];
int n,m;
long long ans;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline int getString(){
char t;
int tot=0;
do{t=getchar();}while(t!='R'&&t!='B');
do{a[++tot]=(t=='R');t=getchar();}while(t=='R'||t=='B');
return tot;
}
inline long long calc(int i,int j){
long long sum,cnt,res;
sum=front[j]-front[i-1];
cnt=frontc[j]-frontc[i-1];
res=sum-cnt*(i-1)-cnt*(cnt+1)/2;
sum=back[j+1]-back[i+n];
cnt=backc[j+1]-backc[i+n];
res+=sum-cnt*(m-(i+n-1))-cnt*(cnt+1)/2;
return res;
}
inline void solve(){
//预处理前缀和后缀和
for(int i=1;i<=m;++i)
front[i]=front[i-1]+i*a[i],frontc[i]=frontc[i-1]+a[i];
for(int i=m;i;--i)
back[i]=back[i+1]+(m-i+1)*a[i],backc[i]=backc[i+1]+a[i];
//枚举环的断点
int pos=1;
long long res;
for(int i=1;i<=n;++i){
long long nans=calc(i,pos);
pos=max(pos,i);
while(pos+1<i+n&&(res=calc(i,pos+1))<=nans){
nans=res;
++pos;
}
ans=min(ans,nans);
}
}
int QAQ(){
int t=read();
while(t--){
n=getString();
m=n<<1;
ans=inf;
for(int i=1;i<=n;++i)
a[i+n]=a[i];
solve();
printf("%lld\n",ans);
}
return false;
}
}
int main(){
return orz::QAQ();
}
[P4169]天使玩偶
cdq模板题?
我们只考虑左下角的贡献,哈曼顿距离直接减一下就出来了。
因为有四个方向,所以跑四个方向。
洛谷上这题卡常,卡卡就过了。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=1000;
const int inf=1047483647;
int mxx,mxy;
int x[N],y[N],type[N];
int p[N];
int ans[N];
int fans[N];
int q[N];
int c[N];
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void remove(int x){
for(;x<=mxy;x+=x&-x)
c[x]=-inf;
}
inline void add(int x,int k){
for(;x<=mxy;x+=x&-x)
c[x]=max(c[x],k);
}
inline int ask(int x){
int ans=-inf;
for(;x;x-=x&-x)
ans=max(ans,c[x]);
return ans;
}
inline void cdq(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq(1,mid),cdq(mid+1,r);
int tl=l,tr=mid+1;
for(int i=l;i<=r;++i){
if((tl<=mid&&x[p[tl]]<=x[p[tr]])||tr>r){
if(!type[p[tl]]){
add(y[p[tl]],x[p[tl]]+y[p[tl]]);
}
q[i]=p[tl++];
}
else {
if(type[p[tr]]){
ans[p[tr]]=max(ans[p[tr]],ask(y[p[tr]]));
}
q[i]=p[tr++];
}
}
for(int i=l;i<=mid;++i)remove(y[p[i]]);
for(int i=l;i<=r;++i)p[i]=q[i];
}
int QAQ(){
freopen("qaq.in","r",stdin);
int n,m;
n=read(),m=read();
for(int i=1;i<=n;++i)
mxx=max(mxx,x[i]=read()),mxy=max(mxy,y[i]=read()),p[i]=i;
for(int i=n+1;i<=m+n;++i)
type[i]=read()-1,p[i]=i,mxx=max(mxx,x[i]=read());mxy=max(mxy,y[i]=read());
for(int i=1;i<=n+m;++i)
fans[i]=inf,ans[i]=-inf,c[i]=-inf;
++mxx;++mxy;
cdq(1,n+m);
for(int i=1;i<=n+m;++i)
fans[i]=min(fans[i],x[i]+y[i]-ans[i]);
for(int i=1;i<=n+m;++i)
x[i]=mxx-x[i],p[i]=i,ans[i]=-inf;
cdq(1,n+m);
for(int i=1;i<=n+m;++i)
fans[i]=min(fans[i],x[i]+y[i]-ans[i]);
for(int i=1;i<=n+m;++i)
y[i]=mxy-y[i],p[i]=i,ans[i]=-inf;
cdq(1,n+m);
for(int i=1;i<=n+m;++i)
fans[i]=min(fans[i],x[i]+y[i]-ans[i]);
for(int i=1;i<=n+m;++i)
x[i]=mxx-x[i],p[i]=i,ans[i]=-inf;
cdq(1,n+m);
for(int i=1;i<=n+m;++i)
fans[i]=min(fans[i],x[i]+y[i]-ans[i]);
for(int i=1;i<=n+m;++i)
if(type[i])
printf("%d\n",fans[i]);
return false;
}
}
int main(){
return orz::QAQ();
}
[NOI2007]货币兑换
原题链接
出现在CDQ专题所以是CDQ。
肯定是个DP
因为n比较大,而且只有n是整数,所以dp数组只能有一维(可以多半维)
题目下面提示了一定有一种最优方案是在每一天都全部卖出或全部买进(或什么也不干)。
设\(f_{i,1|0}\)表示到第i天全卖出的/全买进的最大资产。
最大资产的意思是RMB或金券,金券也用RMB表示
最后答案为\(f_{n,1}\)
因为最后一天必卖。
我们先简化原题中的购买
设我们现在用x的价格来购买,购买到A券的数量为a,B券的数量为b
\(\begin{cases}\frac{a}{b}=K;\\
aA+bB=x\\
\end{cases}\)
解得
\(\begin{cases}
a=\frac{xK}{AK+B}\\
b=\frac{x}{AK+B}
\end{cases}\)
因为操作被简化为了全买全卖,所以操作是一买一卖,所以1的转移去找0,0的转移去找1。
\(f_{i,1}=max(f_{j,0}+A_i\frac{f_{j,0}K_j}{A_jK_j+B_j}+B_i\frac{f_{j,0}}{A_jK_j+B_j})\)
\(f_{i,0}=max(f_{j,1})\)
看着转移的式子我们发现第二维好像没有什么用,所以我们可以把它省略掉。
设f_i表示第i天全卖的最多RMB。
因为一天可以多次操作,所以全买等于全卖后全买。
\(f_i=max(f_{i-1},A_i\frac{f_jK_j}{A_jK_j+B_j}+B_i\frac{f_j}{A_jK_j+B_j})\)
我们就有了一个\(n^2\)的暴力
暴力拿了60分开心,之后的分数据说是斜率优化加CDQ维护凸包/Splay维护凸包。
那我好像不会。
我们先把斜率优化的式子推出来。
设\(g_i=\frac{K_i}{A_iK_i+B_i}\)
我们就可以把那一大坨式子简化一下。
\(f_i=max(f_{i-1},A_iK_jg_jf_j+B_ig_jf_j)\)
设j>k且j优于k
\(A_iK_jg_jf_j+B_ig_jf_j>A_iK_kg_kf_k+B_ig_kf_k\)
[P3414]CPU监控
这题太毒瘤了
首先一眼看出来维护一个历史最大值,每一回更新最大值时更新。
那就WA了。
问题在哪里呢?
因为要写LazyTag ,所以有可能一个更大的操作被后来一个减的操作更新了,导致无法更新下面的历史最大值。
我们考虑LazyTag的本质,它的本质是对一个操作序列的延迟,而一般为了保证复杂度,我们的LazyTag要求支持合并。
而正是这个要求导致了我们这道题会有肯。
我们可以把操作的更新视作是一个长江后浪推前浪的过程。我们的更新会卡在一个位置上,当两坨更新合并的时候,如果我们记下其中的最大值,我们就可以一直用这个最大值去更新后面。
所以我们设6个变量:
mx:最大值
hmx:历史最大值
add:和普通线段树一样
cover:和普通线段树一样
hadd:这一段操作序列的历史最大add值
hcover:这一段操作序列的历史最大cover值
之后我们要参考这个定义来确定转移顺序,所以这个题的pushdown非常恶心。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
#define lc x<<1
#define rc x<<1|1
const int N=1010000;
const int inf=-2147483647;
struct node{
int l,r,mx,hmx,cover,hcover,add,hadd;
}t[(N<<2)+233];
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline int getOrder(){
char t;
do{t=getchar();}while(t>'Z'||t<'A');
switch(t){
case 'Q':
return 1;
case 'A':
return 2;
case 'P':
return 3;
case 'C':
return 4;
}
return false;
}
inline void pushdown(int x){
if(!t[x].add&&t[x].cover==inf)return;
for(int i=(lc);i<=(rc);++i){
//下传历史标记
//更新hmx
t[i].hmx=max(t[i].hmx,max(t[x].hcover,t[x].hadd+t[i].mx));
//下传hadd
if(t[i].cover^inf)t[i].hcover=max(t[i].hcover,t[i].cover+t[x].hadd);
else t[i].hadd=max(t[i].hadd,t[i].add+t[x].hadd);
//下传hcover
t[i].hcover=max(t[i].hcover,t[x].hcover);
//下传add
if(t[x].add){
if(t[i].cover^inf)t[i].cover+=t[x].add;
else t[i].add+=t[x].add;
t[i].mx+=t[x].add;
}
//下传cover
if(t[x].cover^inf){
t[i].mx=t[i].cover=t[x].cover;t[i].add=0;
}
//更新hadd,hcover
t[i].hcover=max(t[i].hcover,t[i].cover);
t[i].hadd=max(t[i].hadd,t[i].add);
}
//清空标记
t[x].cover=t[x].hcover=inf;
t[x].add=t[x].hadd=0;
}
inline void build(int x,int l,int r){
t[x].l=l,t[x].r=r;
t[x].cover=t[x].hcover=inf;
if(l==r){
t[x].hmx=t[x].mx=read();
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
t[x].hmx=t[x].mx=max(t[lc].mx,t[rc].mx);
}
inline void add(int x,int l,int r,int k){
pushdown(x);
if(t[x].l==l&&t[x].r==r){
t[x].add=k;
t[x].hadd=k;
t[x].mx+=k;
t[x].hmx=max(t[x].hmx,t[x].mx);
return;
}
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)add(lc,l,r,k);
else if(l>mid)add(rc,l,r,k);
else add(lc,l,mid,k),add(rc,mid+1,r,k);
t[x].mx=max(t[lc].mx,t[rc].mx);
t[x].hmx=max(t[lc].hmx,t[rc].hmx);
}
inline void cover(int x,int l,int r,int k){
pushdown(x);
if(t[x].l==l&&t[x].r==r){
t[x].cover=k;
t[x].hcover=k;
t[x].mx=k;
t[x].hmx=max(t[x].hmx,t[x].mx);
return;
}
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)cover(lc,l,r,k);
else if(l>mid)cover(rc,l,r,k);
else cover(lc,l,mid,k),cover(rc,mid+1,r,k);
t[x].mx=max(t[lc].mx,t[rc].mx);
t[x].hmx=max(t[lc].hmx,t[rc].hmx);
}
inline int query(int x,int l,int r){
pushdown(x);
if(t[x].l==l&&t[x].r==r)return t[x].mx;
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)return query(lc,l,r);
else if(l>mid)return query(rc,l,r);
else return max(query(lc,l,mid),query(rc,mid+1,r));
}
inline int queryh(int x,int l,int r){
pushdown(x);
if(t[x].l==l&&t[x].r==r)return t[x].hmx;
int mid=(t[x].l+t[x].r)>>1;
if(r<=mid)return queryh(lc,l,r);
else if(l>mid)return queryh(rc,l,r);
else return max(queryh(lc,l,mid),queryh(rc,mid+1,r));
}
int QAQ(){
int n,m;
int l,r,k;
n=read();
build(1,1,n);
m=read();
while(m--){
switch(getOrder()){
case 1:
l=read(),r=read();
printf("%d\n",query(1,l,r));
break;
case 2:
l=read(),r=read();
printf("%d\n",queryh(1,l,r));
break;
case 3:
l=read(),r=read(),k=read();
add(1,l,r,k);
break;
case 4:
l=read(),r=read(),k=read();
cover(1,l,r,k);
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
[P4930]采药人的路径
原题链接
一句话题意:给定一棵每条边边权为1/-1的树,问有多少条路径满足其中至少有一个不为端点的点,到两端点的权值和为0。
这是一道点分治题。
我们考虑对于一个点如何计算通过这个点的合法路径条数。
对于一个合法的路径,和一个必经的点,至少组成它的两条链中有一条链存在一个后缀为0的位置。
所以我们在点分治的时候分两种路径,一种是存在后缀为0的,一种是不存在后缀为0的,我们在两种路径之间进行更新就可以。
对于后缀为零我们可以维护一个栈,如果栈中的长度再次出现,那么这两个长度之间为0。
比较麻烦的是休息点在根上的路径,我们需要另外把它加上。
为了统计单在一棵子树上的合法路径,我们还需要一开始插入一个长度为0,不存在后缀为0的路径。
GMK ORZ
Code
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=300000;
const int inf=2147483647;
int head[N],ver[N],next[N],edge[N],tot;
int rootmax,size[N],all,root;
int k;
int dis[2][N],pre[2][N],mem[2][N];
int stack[N];
bool rm[N];
long long ans=0;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y,int z){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=z;
}
void getroot(int x,int father){
int maxpart=0,y;
size[x]=1;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(rm[y])continue;
if(y==father)continue;
getroot(y,x);
size[x]+=size[y];
maxpart=max(maxpart,size[y]);
}
maxpart=max(maxpart,all-size[x]);
if(maxpart<rootmax)root=x,rootmax=maxpart;
}
void getdis(int x,int father,int dep){
if(stack[dep+k])dis[1][++dis[1][0]]=dep,mem[1][++mem[1][0]]=dep+k;
else dis[0][++dis[0][0]]=dep,mem[0][++mem[0][0]]=dep+k;
++stack[dep+k];
for(int i=head[x];i;i=next[i]){
if(rm[ver[i]])continue;
if(ver[i]==father)continue;
getdis(ver[i],x,dep+edge[i]);
}
--stack[dep+k];
}
void solve(int x,int sum){
all=sum;
rootmax=inf;
getroot(x,0);
x=root;
rm[x]=true;
mem[0][0]=mem[1][0]=0;
pre[0][0+k]=1;
for(int i=head[x];i;i=next[i]){
if(rm[ver[i]])continue;
dis[0][0]=dis[1][0]=0;
getdis(ver[i],x,edge[i]);
for(int i=1;i<=dis[0][0];++i){
ans+=(long long)pre[1][-dis[0][i]+k];
if(dis[0][i]==0)ans+=(long long)pre[0][k]-1;
}
for(int i=1;i<=dis[1][0];++i)
ans+=(long long)pre[1][-dis[1][i]+k]+pre[0][-dis[1][i]+k];
for(int i=1;i<=dis[0][0];++i)
++pre[0][dis[0][i]+k];
for(int i=1;i<=dis[1][0];++i)
++pre[1][dis[1][i]+k];
}
for(int i=1;i<=mem[0][0];++i)
pre[0][mem[0][i]]=0;
for(int i=1;i<=mem[1][0];++i)
pre[1][mem[1][i]]=0;
for(int i=head[x];i;i=next[i]){
if(rm[ver[i]])continue;
solve(ver[i],size[ver[i]]);
}
}
int QAQ(){
int n;
int x,y,z;
n=read();
k=n+23;
for(int i=1;i<n;++i){
x=read(),y=read(),z=read();
if(!z)--z;
add(x,y,z);
}
solve(1,n);
printf("%lld\n",ans);
return false;
}
}
int main(){
return orz::QAQ();
}