虽然考的很差,很不想去再面对这套题,但是只有直面失败才能走向成功。从新审视这套题,才发现自己存在的问题和差距。
mobius反演。。。
∏ni=1∏mj=1fi[gcd(i,j)]
∏nk=1fi[k]∑ni=1∑mj=1[gcd(i,j)=k]
设 f(d)=∑ni=1∑mj=1[gcd(i,j)=k] ,表示最大公约数为k的数对数
F(d)=⌊nd⌋∗⌊md⌋ 表示公约数为k的数对数
根据莫比乌斯反演的公式 f(d)=∑d|nμ(nd)∗F(n)
所以式子可以变成
#include
#include
#include
#include
#include
#define N 1000000
#define LL long long
#define p 1000000007
#define mod 1000000006
using namespace std;
int pd[N+3],prime[N+3],n,m,T;
LL f[N+3],mu[N+3],inv[N+3],cnt[N+3],fi[N+3];
LL quickpow(LL num,LL x)
{
x=(x%mod+mod)%mod;
LL base=num%p; LL ans=1;
while (x) {
if (x&1) ans=ans*base%p;
x>>=1;
base=base*base%p;
}
return ans;
}
void init()
{
mu[1]=1; fi[0]=0; fi[1]=1; inv[0]=1;
for (int i=2;i<=N;i++) fi[i]=(fi[i-1]+fi[i-2])%p;
for (int i=2;i<=N;i++) {
if (!pd[i]) {
prime[++prime[0]]=i;
mu[i]=-1;
}
for (int j=1;j<=prime[0];j++) {
if (i*prime[j]>N) break;
pd[i*prime[j]]=1;
if (i%prime[j]==0) {
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for (int i=0;i<=N;i++) f[i]=1;
for (int i=1;i<=N;i++) {
for (int j=i,t=1;j<=N;j+=i,t++)
f[j]=f[j]*quickpow(fi[i],mu[t])%p;
}
for (int i=2;i<=N;i++) f[i]=f[i]*f[i-1]%p;
for (int i=1;i<=N;i++) inv[i]=quickpow(f[i],p-2);
}
int main()
{
freopen("product.in","r",stdin);
freopen("product.out","w",stdout);
scanf("%d",&T);
init();
while (T--){
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
LL ans=1;
for (int i=1,j;i<=n;i=j+1) {
j=min(n/(n/i),m/(m/i));
LL t=(LL)(n/i)*(m/i);
ans=ans*quickpow(f[j]*inv[i-1],t)%p;
}
printf("%I64d\n",ans);
}
}
LCT+线段树
感觉自己对LCT一直有抵触情绪。。。。所以决定好好写这道题的题解。
一条路径的权值为:路径上的颜色种类和。
我们定义f(x),表示x与fa[x]的颜色是否相同,相同为0,不同为1,令 f(1)=1。g(x)表示x到root路径上的f的和。然后考虑怎么维护g(x)。
因为是一颗有根树,所以我们不牵扯到换根操作,最初的时候所以的节点都是指向他的父节点的。(儿子认父亲,父亲不认儿子)。lct维护的splay中的信息,一定是一条重链的信息。对于这条链来说,splay中所有节点的颜色都是相同的。
现在我们要将x到root的路径染成一种新的颜色,利用access操作实现对节点的修改。
access中有一个砍重儿子的过程,对于上图中的三号紫点来说,砍掉了四号紫点(不是单独的一个点,而是四号紫点所在的splay),对于四号紫点所在的splay维护的重链的链顶节点(就是三号紫点真正的儿子)来说,他子树中的所有点g值都会增加1。
现在一号蓝点变成了三号紫点的重儿子,那么对应的一号蓝点所在的splay维护的重链的链顶节点子树中所有点的g值都减少1.
根据access的过程假设当前点是三号紫点,那么他与root在一棵splay中,转根后他就是splay的根,fa[x]就是0,不在需要更改任何g的值。我们发现对于1-3号紫点他们的g值在染色过程中是不发生改变的,所以可以用lct科学的维护。
那么g值得改变都是针对子树的,所以我们搞出dfs序,那么每次修改都是修改的一段区间,就变成了线段树的区间修改。
对于操作3,直接进行区间查询即可。
对于操作2,x->y 的答案就是g(x)+g(y)-2*g(lca(x,y))+1,进行三次单点查询。
#include
#include
#include
#include
#include
#define N 400003
using namespace std;
int point[N],nxt[N],v[N],tr[N],delta[N],l[N],r[N],deep[N],mi[20];
int n,m,k,col[N],f[N][20],fa[N],ch[N][3],tot,sz,pos[N],cur[N];
void add(int x,int y)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void dfs(int rt)
{
int x=rt;
while (true) {
if (!deep[x]) {
deep[x]=deep[f[x][0]]+1;
for (int i=1;i<=17;i++) {
if (deep[x]-mi[i]<0) break;
f[x][i]=f[f[x][i-1]][i-1];
}
l[x]=r[x]=++sz; pos[sz]=x; cur[x]=point[x];
}
bool pd=false;
for (int i=cur[x];i;i=nxt[i]) {
cur[x]=nxt[i];
if (v[i]!=f[x][0]) {
f[v[i]][0]=fa[v[i]]=x; x=v[i]; pd=true;
break;
}
}
if (!pd) {
int t=f[x][0];// cout<
r[t]=max(r[t],r[x]);
if (x==rt) break;
x=t;
}
}
}
int lca(int x,int y)
{
if (deep[x]int k=deep[x]-deep[y];
for (int i=0;i<=17;i++)
if ((k>>i)&1) x=f[x][i];
if (x==y) return x;
for (int i=17;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void update(int now)
{
tr[now]=max(tr[now<<1],tr[now<<1|1]);
}
void pushdown(int now)
{
if (delta[now]) {
tr[now<<1]+=delta[now]; tr[now<<1|1]+=delta[now];
delta[now<<1]+=delta[now]; delta[now<<1|1]+=delta[now];
delta[now]=0;
}
}
void qjchange(int now,int l,int r,int ll,int rr,int val)
{
if (ll<=l&&r<=rr) {
tr[now]+=val; delta[now]+=val;
return;
}
int mid=(l+r)/2;
pushdown(now);
if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,val);
if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,val);
update(now);
}
int find(int now,int l,int r,int x)
{
if (l==r) return tr[now];
int mid=(l+r)/2;
pushdown(now);
if (x<=mid) return find(now<<1,l,mid,x);
else return find(now<<1|1,mid+1,r,x);
}
int query(int now,int l,int r,int ll,int rr)
{
if (ll<=l&&r<=rr) return tr[now];
int mid=(l+r)/2; int ans=0;
pushdown(now);
if (ll<=mid) ans=max(ans,query(now<<1,l,mid,ll,rr));
if (rr>mid) ans=max(ans,query(now<<1|1,mid+1,r,ll,rr));
return ans;
}
bool isroot(int x)
{
return ch[fa[x]][1]!=x&&ch[fa[x]][0]!=x;
}
int get(int x)
{
return ch[fa[x]][1]==x;
}
void rotate(int x)
{
int y=fa[x]; int z=fa[y]; int which=get(x);
if (!isroot(y)) ch[z][ch[z][1]==y]=x;
fa[x]=z; fa[y]=x; ch[y][which]=ch[x][which^1];
fa[ch[x][which^1]]=y; ch[x][which^1]=y;
}
void splay(int x)
{
int y;
while (!isroot(x)){
y=fa[x];
if (!isroot(y)) rotate(get(y)==get(x)?y:x);
rotate(x);
}
}
int get_root(int x)
{
while (ch[x][0]) x=ch[x][0];
return x;
}
void access(int x)
{
int t=0;
while (x) {
col[x]=k;
splay(x);
int t1=get_root(ch[x][1]);
if (t1) qjchange(1,1,n,l[t1],r[t1],1);
ch[x][1]=t;
int t2=get_root(t);
if (t2) qjchange(1,1,n,l[t2],r[t2],-1);
t=x; x=fa[x];
}
}
int main()
{
freopen("paint.in","r",stdin);
freopen("paint.out","w",stdout);
scanf("%d%d",&n,&m); k=n; mi[0]=1;
for (int i=1;i<=18;i++) mi[i]=mi[i-1]*2;
for (int i=1;iint x,y; scanf("%d%d",&x,&y);
add(x,y);
}
dfs(1);
for (int i=1;i<=n;i++) qjchange(1,1,n,l[i],r[i],1);
for (int i=1;i<=m;i++) {
int opt,x,y; scanf("%d%d",&opt,&x);
if (opt==1) k++,access(x);
if (opt==2) {
scanf("%d",&y); int t=lca(x,y);
printf("%d\n",find(1,1,n,l[x])+find(1,1,n,l[y])-2*find(1,1,n,l[t])+1);
}
if (opt==3) printf("%d\n",query(1,1,n,l[x],r[x]));
}
}
DP+矩阵乘法
至少有一个数是质数的方案数=无限制的方案数-只有质数的方案数
预处理出转移矩阵,直接上矩阵快速幂即可。
#include
#include
#include
#include
#include
#define N 20000003
#define M 103
#define mod 20170408
#define LL long long
using namespace std;
bool pd[N];
int prime[N],a[M],b[M],n,m,p;
struct data{
LL a[M][M];
}e,c;
void init()
{
for (int i=2;i<=m;i++) {
if(!pd[i]) prime[++prime[0]]=i;
for (int j=1;j<=prime[0];j++) {
if (i*prime[j]>m) break;
pd[i*prime[j]]=1;
if (i%prime[j]==0) break;
}
}
for (int i=1;i<=m;i++) a[i%p]++;
for (int i=1;i<=prime[0];i++) b[prime[i]%p]++;
for (int i=0;i//for (int i=0;i
" "; cout<// for (int i=0;i" "; cout<for (int i=0;ifor (int j=0;j
0;
}
data mul(data a,data b)
{
data c;
for (int i=0;i
for (int j=0;j
0;
for (int k=0;k
*b.a[k][j]%mod)%mod;
}
return c;
}
data quickpow(data num,int x){
data base=num; data ans=c;
while (x) {
if (x&1) ans=mul(ans,base);
x>>=1;
base=mul(base,base);
}
return ans;
}
LL solve(int a[])
{
clear(e);
for (int i=0;i
for (int j=0;j
%p]+=a[j],e.a[i][(i+j)%p]%=mod;
data ans=quickpow(e,n);
return ans.a[0][0];
}
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%d%d%d",&n,&m,&p);
init();
for (int i=0;i
1;
LL t=solve(a)-solve(b);
printf("%I64d\n",(t%mod+mod)%mod);
}
01分数规划+费用流。
看到 C=a′1+a′2+...+a′nb′1+b′2+...+b′n 就应该想到01分数规划。
先考虑如果每两个人之间只有一个有关的权值该怎么做?那么问题就变成了最大权匹配。这个貌似有一个叫做KM的算法可以快速求解,但是费用流也很好用啊。
S−>i 容量为1,费用为0
j−>T 容量为1,费用为0
i−>j 容量为1,费用为 val[i][j]
二分答案,然后将边权赋值成 a[i][j]−mid∗b[i][j] ,跑最大费用最大流,如果最后的费用为正,说明答案还可以更大。
据学长说,把double运算变成整数运算会快哦。
#include
#include
#include
#include
#include
#include
#define N 50003
#define inf 1000000000
#define eps 1e-9
using namespace std;
int n,m,tot,point[N],nxt[N],v[N],remain[N],last[N],can[N],S,T;
double a[203][230],b[203][203],len[N],dis[N],ans;
int head,tail,q[N*10];
void add(int x,int y,int z,double k)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; len[tot]=k;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; len[tot]=-k;
}
int addflow(int s,int t)
{
int now=t; int ans=inf;
while (now!=s) {
ans=min(ans,remain[last[now]]);
now=v[last[now]^1];
}
now=t;
while (now!=s) {
remain[last[now]]-=ans;
remain[last[now]^1]+=ans;
now=v[last[now]^1];
}
return ans;
}
bool spfa(int s,int t)
{
for (int i=1;i<=t;i++) dis[i]=-inf,can[i]=0;
can[s]=1; dis[s]=0;
head=0; tail=0; q[++tail]=s;
while (headint now=q[++head];
for (int i=point[now];i!=-1;i=nxt[i])
if (dis[v[i]]last[v[i]]=i;
if (!can[v[i]]) {
can[v[i]]=1;
q[++tail]=v[i];
}
}
can[now]=0;
}
if (dis[t]==-inf) return false;
int flow=addflow(s,t);
ans+=dis[t]*flow;
return true;
}
void solve(int s,int t)
{
while (spfa(s,t));
}
bool check(double mid)
{
tot=-1;
memset(point,-1,sizeof(point));
S=1; T=2*n+2;
for (int i=1;i<=n;i++) add(S,i+1,1,0);
for (int i=1;i<=n;i++) add(i+n+1,T,1,0);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) add(i+1,j+n+1,1,a[i][j]-mid*b[i][j]);
//cout<0; solve(S,T);
return ans>=-eps;
}
int main()
{
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
scanf("%d",&n); double sum=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) scanf("%lf",&a[i][j]),sum=max(sum,a[i][j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) scanf("%lf",&b[i][j]);
double l=0; double r=sum; double ans=0;
while (r-l>=eps) {
double mid=(l+r)/2;
if (check(mid)) ans=max(ans,mid),l=mid+eps;
else r=mid-eps;
}
printf("%.6lf\n",ans);
}
KMP+高斯消元
设N为未结束状态的概率。
假设用两个串TTH和HTT,设第一个获胜的概率是A,第二个人获胜的概率为B
如果在N后面加上TTH,那么有三种可能
NTTH=A+BTH+BH ,是什么意思呢?就是如果在N后面加入TTH,那么第一个人猜的序列出现在了硬币序列中,第一个人获胜,但是N是什么我们不清楚,但是有可能到达第一个T或者第二个T的时候第二个人就获胜了。
所以对于状态NTTH,可以由三个状态得到。
123N=A+12B+122B,12 表示的是正反面的概率。
这样我们得到了n个方程,但是有n+1个未知量,因为所有人获胜的总概率是1,那么我们加入这个方程就成功得到了n+1个未知量,n+1个方程。高斯消元直接解就可以了
那么我们怎么得到每个串之间类似A,B的关系呢?发现满足A的前缀是B的后缀,这不就是失配的定义么。所以直接将两个串连起来,用KMP求失配即可。系数就是 12m−末位失配 ,需要注意的是系数应该是末位失配一直沿着fail指针向前跳,经过的所有点的概率和。
#include
#include
#include
#include
#include
#define N 1003
using namespace std;
double mi[N],a[N][N],b[N],ans[N];
int n,m,t[N],len;
char s[N][N],ch[N];
void gauss(int n)
{
for (int i=1;i<=n;i++){
int num=i;
for (int j=i+1;j<=n;j++)
if (fabs(a[j][i])>fabs(a[num][i])) num=j;
if (num!=i) {
for (int j=1;j<=n;j++) swap(a[num][j],a[i][j]);
swap(b[num],b[i]);
}
for (int j=i+1;j<=n;j++)
if (a[j][i]) {
double t=a[j][i]/a[i][i];
for (int k=1;k<=n;k++)
a[j][k]-=a[i][k]*t;
b[j]-=b[i]*t;
}
}
for (int i=n;i>=1;i--){
ans[i]=b[i];
for (int j=i+1;j<=n;j++)
if (a[i][j]) ans[i]-=ans[j]*a[i][j];
ans[i]/=a[i][i];
}
}
void calc()
{
t[1]=0;
for (int i=1;i<=len;i++) {
int j=t[i];
while (ch[j]!=ch[i]&&j) j=t[j];
t[i+1]=j+1;
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%s",s[i]+1);
mi[0]=1.0;
for (int i=1;i<=m;i++) mi[i]=mi[i-1]*0.5;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) {
len=0;
for (int k=1;k<=m;k++) ch[++len]=s[i][k];
for (int k=1;k<=m;k++) ch[++len]=s[j][k];
calc();
int k=t[len+1];
while (k>1) {
a[i][j]+=mi[m-k+1];
k=t[k];
}
}
double p=1.0/(double)(1<for (int i=1;i<=n;i++) a[i][n+1]=-p;
for (int i=1;i<=n;i++) a[n+1][i]=1.0;
b[n+1]=1;
gauss(n+1);
for (int i=1;i<=n;i++) printf("%.7lf\n",ans[i]);
}
线段树
首先对a的求解式子进行变形
写的过程中有一个小细节需要注意,就i*i可能会炸int,注意加强转啊!!!
#include
#include
#include
#include
#include
#define N 200003
#define eps 1e-9
#define inf 1000000000
using namespace std;
struct data {
double sumx,sumy,xy,sq,sum,squ;
double tagx,tagy,cx,cy;
}tr[N*4];
double xi[N],yi[N];
int n,m;
data update(data l,data r)
{
data now; now.tagx=now.tagy=0;
now.cx=now.cy=inf;
now.sumx=l.sumx+r.sumx;
now.sumy=l.sumy+r.sumy;
now.xy=l.xy+r.xy;
now.sq=l.sq+r.sq;
now.squ=l.squ+r.squ;
now.sum=l.sum+r.sum;
return now;
}
void build(int now,int l,int r)
{
if(l==r) {
tr[now].sumx=xi[l]; tr[now].sumy=yi[l];
tr[now].xy=xi[l]*yi[l]; tr[now].sq=xi[l]*xi[l];
tr[now].sum=l; tr[now].squ=(double)l*(double)l;
return;
}
int mid=(l+r)/2;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
tr[now]=update(tr[now<<1],tr[now<<1|1]);
}
void calc(int now,int l,int r,double valx,double valy)
{
double len=(r-l+1);
tr[now].tagx+=valx; tr[now].tagy+=valy;
tr[now].sq+=len*valx*valx+2.0*valx*tr[now].sumx;
tr[now].xy+=valx*tr[now].sumy+valy*tr[now].sumx+valx*valy*len;
tr[now].sumx+=valx*len; tr[now].sumy+=valy*len;
if (tr[now].cx==inf&&tr[now].cy==inf) return;
tr[now].cx+=valx; tr[now].cy+=valy;
tr[now].tagx=tr[now].tagy=0;
}
void cover(int now,int l,int r,double valx,double valy)
{
double len=(r-l+1);
tr[now].cx=valx; tr[now].cy=valy; tr[now].tagx=tr[now].tagy=0;
tr[now].sq=tr[now].squ+tr[now].sum*2.0*valx+valx*valx*len;
tr[now].xy=tr[now].squ+(valx+valy)*tr[now].sum+valx*valy*len;
tr[now].sumx=tr[now].sum+len*valx;
tr[now].sumy=tr[now].sum+len*valy;
}
void pushdown(int now,int l,int r)
{
int mid=(l+r)/2;
if (tr[now].cx!=inf||tr[now].cy!=inf){
cover(now<<1,l,mid,tr[now].cx,tr[now].cy);
cover(now<<1|1,mid+1,r,tr[now].cx,tr[now].cy);
tr[now].cx=tr[now].cy=inf;
}
if (fabs(tr[now].tagx)>=eps||fabs(tr[now].tagy)>=eps) {
calc(now<<1,l,mid,tr[now].tagx,tr[now].tagy);
calc(now<<1|1,mid+1,r,tr[now].tagx,tr[now].tagy);
tr[now].tagx=0; tr[now].tagy=0;
}
}
data query(int now,int l,int r,int ll,int rr)
{
if (ll<=l&&r<=rr) return tr[now];
int mid=(l+r)/2;
pushdown(now,l,r); data ans; bool pd=false;
if (ll<=mid) ans=query(now<<1,l,mid,ll,rr),pd=true;
if (rr>mid) {
if (pd) ans=update(ans,query(now<<1|1,mid+1,r,ll,rr));
else ans=query(now<<1|1,mid+1,r,ll,rr);
}
return ans;
}
void qjchange(int now,int l,int r,int ll,int rr,double valx,double valy)
{
if (ll<=l&&r<=rr) {
calc(now,l,r,valx,valy);
return;
}
int mid=(l+r)/2;
pushdown(now,l,r);
if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,valx,valy);
if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,valx,valy);
tr[now]=update(tr[now<<1],tr[now<<1|1]);
}
void qjcover(int now,int l,int r,int ll,int rr,double valx,double valy)
{
if (ll<=l&&r<=rr) {
cover(now,l,r,valx,valy);
return;
}
int mid=(l+r)/2;
pushdown(now,l,r);
if (ll<=mid) qjcover(now<<1,l,mid,ll,rr,valx,valy);
if (rr>mid) qjcover(now<<1|1,mid+1,r,ll,rr,valx,valy);
tr[now]=update(tr[now<<1],tr[now<<1|1]);
}
int main()
{
freopen("relative.in","r",stdin);
freopen("relative.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%lf",&xi[i]);
for (int i=1;i<=n;i++) scanf("%lf",&yi[i]);
build(1,1,n);
for (int i=1;i<=m;i++) {
int opt,l,r; scanf("%d",&opt);
if (opt==1) {
scanf("%d%d",&l,&r);
data a=query(1,1,n,l,r); double len=r-l+1;
double ans=0,ans1=0; double bx=a.sumx/len; double by=a.sumy/len;
ans=a.xy-bx*a.sumy-by*a.sumx+bx*by*len;
ans1=len*bx*bx+a.sq-2.0*bx*a.sumx;
printf("%.10lf\n",ans/ans1);
}
if (opt==2){
double s,t;
scanf("%d%d%lf%lf",&l,&r,&s,&t);
qjchange(1,1,n,l,r,s,t);
}
if (opt==3) {
double s,t; scanf("%d%d%lf%lf",&l,&r,&s,&t);
qjcover(1,1,n,l,r,s,t);
}
}
}