haoi2018

题解:

题目相对其他省难一点

不过弱省省选知识点都这么集中的么。。

4道数学题。。。

1.[HAOI2018]奇怪的背包

这题考场做就gg了。。。

其实我想到了那个性质。。 就是这个一定要是gcd的倍数

但是我傻逼的觉得这个不对。。 因为xi都要>=0

然后就看题解。。

仔细想了一下 这可是模意义下啊??

你要是负数你一直加p答案不是不变的么。。。

然后有了这个性质我们考虑dp

直接$f[i][j]$表示考虑了前i个数,gcd为j这个复杂度比较gg

我们发现j一定是p的因数,所以离散化一下(hash表查找)

设p的因数个数为M

这个复杂度是$nMlogn$的 还是gg。。。

我们再看一看 每次加入一个数实际上是与它取gcd

而gcd一定也是y的因数 所以刚开始本质不同的数只有M种

对于相同的数,不取就一种方案,取就是$(2^k-1)$

然后这样dp就是$M^2logM$

讲道理M的上限是sqrt的。。(不知道差了多少倍 程序要跑太久。。)

但反正是能过了。。

// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define rint register int
#define IL inline
#define rep(i,h,t) for (int i=h;i<=t;i++)
#define dep(i,t,h) for (int i=t;i>=h;i--)
#define ll long long
#define me(x) memset(x,0,sizeof(x))
#define mid ((h+t)>>1)
using namespace std;
const int INF=1e9;
namespace IO{
    char ss[1<<24],*A=ss,*B=ss;
    IL char gc()
    {
        return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++;
    }
    template<class T>void read(T &x)
    {
        rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48);
        while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f;
    }
    char sr[1<<24],z[20]; int Z,C=-1;
    template<class T>void wer(T x)
    {
        if (x<0) sr[++C]='-',x=-x;
        while (z[++Z]=x%10+48,x/=10);
        while (sr[++C]=z[Z],--Z);
    }
    IL void wer1()
    {
        sr[++C]=' ';
    }
    IL void wer2()
    {
        sr[++C]='
';
    }
    template<class T>IL void maxa(T &x,T y) {if (x<y) x=y;}
    template<class T>IL void mina(T &x,T y) {if (x>y) x=y;}
    template<class T>IL T MAX(T x,T y) {return x>y?x:y;}
    template<class T>IL T MIN(T x,T y) {return x<y?x:y;}
};
using namespace IO;
const int mo=5e6+7;
struct Hash{
    int H[mo+10000],H1[mo+10000];
    IL void push(int x,int z)
    {
        int y=x%mo;
        while (H[y]!=0&&H[y]!=x) ++y;
        H[y]=x; H1[y]=z;
    }
    IL int query(int x)
    {
        int y=x%mo;
        while (H[y]!=0&&H[y]!=x) ++y;
        return H1[y];
    }
}H;
const int N=1e6+1e5;
const int M=3.5e4+100;
int a[N],f[N],pp[M];
int g[2][M],now[M],now2[M],o[N];
int gcd(int x,int y)
{
    return (!y)?x:gcd(y,x%y);
}
const int mo2=1e9+7;
IL void js(register int &x,register int y)
{
    x+y>mo2?x=x+y-mo2:x=x+y;
}
int main()
{
    int n,q,p;
    read(n); read(q); read(p);
    rep(i,1,n) read(a[i]);
    int k=sqrt(p);
    int cnt=0;
    rep(i,1,k)
      if (p%i==0) f[++cnt]=i;
    k=cnt;
    dep(i,k,1)
      if (p!=f[i]*f[i]) f[++cnt]=p/f[i];
    rep(i,1,cnt) H.push(f[i],i);
    rep(i,1,n) pp[H.query(gcd(a[i],p))]++;
    o[0]=1;
    rep(i,1,1000000) o[i]=o[i-1]*2%mo2;
    rep(i,1,1000000) o[i]=((o[i]-1)%mo2+mo2)%mo2;
    g[1][cnt]=1;
    rep(i,1,cnt)
    {
      int *g1=g[i%2],*g2=g[(i+1)%2];
      memset(g[(i+1)%2],0,sizeof(g[0][0])*(cnt+10));
      if (pp[i])
      rep(j,1,cnt)
        if (g1[j])
        {
            js(g2[H.query(gcd(f[i],f[j]))],(1ll*g1[j]*o[pp[i]])%mo2);
        }
      rep(j,1,cnt) js(g2[j],g1[j]);
    }
    memcpy(now,g[(cnt+1)%2],sizeof(g[(cnt+1)%2]));
    rep(i,1,cnt)
    {
      int w=p/f[i];
      int k=sqrt(w);
      rep(j,1,k)
        if (w%j==0)
        {
            int t=H.query(f[i]*j);
            if (t) js(now2[t],now[i]);
            if (j*j!=w)
            {
                t=H.query(f[i]*w/j);
                if (t) js(now2[t],now[i]);
            }
        }
    }
    rep(i,1,q)
    {
        int x; read(x);
        x=H.query(gcd(x,p));
        wer(now2[x]); wer2();
    }
    fwrite(sr,1,C+1,stdout);
    return 0;
}

2.[HAOI2018]反色游戏

这个感觉思路其实不难 但好像并没有往这上面想

第一感觉就是这个可以化成xor然后线性基高斯消元

最后线性基中为0的点就可以任取 答案为2^k 注意还要判断合不合法 不合法直接为0

然后发现就算没有多次询问就一组也过不了???

然后我就觉得应该是有个黑科技我没学过。。。。

先说一下部分分。。

50分暴力高斯消元就可以了 ($n^4$)

60分需要bitset优化($n^4/32$)

// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define IL inline
#define rep(i,h,t) for (int i=h;i<=t;i++)
#define dep(i,t,h) for (int i=t;i>=h;i--)
#define ll long long
#define me(x) memset(x,0,sizeof(x))
const int N=210;
const int mo=1e9+7;
int T,n,m;
struct re{
  int a,b;
};
vector<re> ve[N];
char c[N];
bitset<N> B[N],now; 
int ans[N],f[N],g[2000];
bool cmp(int x,int y)
{
  return x>y;
}
int main()
{
  ios::sync_with_stdio(false);
  g[0]=1;
  rep(i,1,1000) g[i]=(g[i-1]*2)%mo; 
  cin>>T;
  rep(ttt,1,T)
  {
    cin>>n>>m;
    rep(i,1,n) ve[i].clear();
    rep(i,1,m)
    {
      int x,y;
      cin>>x>>y;
      ve[x].push_back((re){i,y}); ve[y].push_back((re){i,x});
    }
    cin>>c;
    rep(i,0,n-1) f[i+1]=c[i]-'0';
    rep(i,0,n)
    {
      bool tt=1;
      int c3=0;
      rep(j,1,n)
        for (int k=0;k<ve[j].size();k++)
          if (j==i||ve[j][k].b==i) c3++;
      c3/=2;
      rep(j,1,m) B[j]=0,ans[j]=0;
      rep(j,1,n)
      if (j!=i)
      {
        now=0; int ans2=f[j];
        for (int k=0;k<ve[j].size();k++)
          if (ve[j][k].b!=i) now[ve[j][k].a]=1;
        dep(k,m,1)
          if (now[k])
          {
            if (B[k]!=0)
            { 
              now^=B[k],ans2^=ans[k];
              if (now==0) break;
            }
            else
            { 
              B[k]=now;
              ans[k]=ans2;
              break;
            }
          }
        if (now==0&&ans2) tt=0;
      }
      int cnt=0;
      rep(j,1,m) if (B[j]==0) cnt++;
      cnt-=c3;
      if (!tt) cout<<0<<" ";
      else cout<<g[cnt]<<" ";
    }
    cout<<endl;
  }
  return 0;
}

70分我不知道是什么东西。。。 我会线段树分治做到($n^3logn/32$) 但这个常数明显大而且肯定过不了70啊。。

然后考虑一下正解

发现这题要用线性基其实是假的

一条边只能连两个节点啊。。。

所以我们考虑合并

如果合并之前两点已经有关系说明这条边就任意了

于是简化一下要求的就是2^(n-m-联通块个数)

另外怎么判断合不合法的充要条件是 如果黑点数为奇数就不合法

来简单证明一下

必要性:改变的颜色数是边数*2的 黑点为奇数显然不行

充分性:如果改变了偶数个,每次我们就任取两个扩展一条他们之前的连边 如果重复就把边取反就可以了

于是我们要支持删一个点求联通块个数和有无奇数个黑点

分类讨论了

3.

由于后缀数组和后缀自动机有点忘了。。以后再补

day2为啥只有两题。。

4.[HAOI2018]苹果树

我用了跟题解不太一样的方法 当然题解更加简单。。

令$f(i)$表示i个点排成子树的方案数 (我刚开始并没有想到这个是n!)

$f(i)=sum C(n,i)*f(i)*f(n-i)$

令$g(i)$表示i个点排成子树到根的路径和

$g(i)=sum$

令$h(i)$表示i个点排成子树内部路径和

$h(i)=sum$

题解的方法也比较常见

考虑每条边的贡献

#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define IL inline
#define rep(i,h,t) for (int i=h;i<=t;i++)
#define dep(i,t,h) for (int i=t;i>=h;i--)
#define ll long long
#define me(x) memset(x,0,sizeof(x))
namespace IO{
    char ss[1<<24],*A=ss,*B=ss;
    IL char gc()
    {
        return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++;
    }
    template<class T>void read(T &x)
    {
        rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48);
        while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f;
    }
    char sr[1<<24],z[20]; int Z,CC=-1;
    template<class T>void wer(T x)
    {
        if (x<0) sr[++CC]='-',x=-x;
        while (z[++Z]=x%10+48,x/=10);
        while (sr[++CC]=z[Z],--Z); 
    }
    IL void wer1()
    {
        sr[++CC]=' ';
    }
    IL void wer2()
    {
        sr[++CC]='
';
    }
    template<class T>IL void mina(T &x,T y) { if (x>y) x=y;}
    template<class T>IL void maxa(T &x,T y) { if (x<y) x=y;}
    template<class T>IL T MIN(T x,T y){return x<y?x:y;}
    template<class T>IL T MAX(T x,T y){return x>y?x:y;}
};
using namespace IO;
const int N=2050;
int nn,mo;
int C[N][N],f[N],g[N],h[N];
IL void js(int &x,int y)
{
    x=x+y>mo?x+y-mo:x+y;
}
int main()
{
    read(nn); read(mo);
    int m=2000;
    rep(i,1,m)
    {
      C[i][0]=C[i][i]=1;
      rep(j,1,i-1)
        C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
    }
    f[0]=f[1]=1;
    rep(i,2,nn)
    {
      int n=i-1;
      rep(j,0,n)
        js(f[i],1ll*f[j]*f[n-j]%mo*C[n][j]%mo);
      rep(j,0,n)
        js(g[i],2ll*g[j]%mo*C[n][j]%mo*f[n-j]%mo);
      js(g[i],1ll*f[i]*n%mo);
      rep(j,0,n)
        js(h[i],2ll*h[j]%mo*C[n][j]%mo*f[n-j]%mo);
      rep(j,0,n)
        js(h[i],(2ll*j%mo*(n-j)%mo*f[j]%mo*f[n-j]%mo+2ll*g[j]%mo*(n-j)%mo*f[n-j]%mo)%mo*C[n][j]%mo);
      js(h[i],g[i]);
    }
 //   rep(i,1,nn) cout<<f[i]<<" "<<g[i]<<" "<<h[i]<<endl; 
    cout<<h[nn]<<endl;
    return 0; 
}

5.[HAOI2018]染色

这个题型比较经典

首先我们考虑取出k个正好有s的

 $C(n,s)*C(n-s,s)*...*C(n-k,s) $然后这个可以化简一下

然后因为这个已经保证了顺序 颜色只需要$C(m,k)$就行了(不是$A(s,k)$)

令$F(n,m)$表示n个点用m种颜色染,没有为k的方案数

这个比较显然可以容斥来做 式子太长不想写。。。

列出式子再带进去

我的ntt方法好像和网上不太一样

$ans=f(i)*sum{g(j)*h(i+j)}$

然后我们变换一下 令$p(i)=h(t-i)$

这样就是卷积形式了

写代码的时候正推预处理了1e7的inv

然后发现这个跑的贼慢。。直接exgcd算比这个快多了

有一个常数更小的方法是先计算$(n!)$的逆元然后再倒推回去

这样就不需要除法和取模了

好久没打ntt。。

ntt的n 应该是算出结果的最高项次数

刚开始没算0的情况

这种题目基本最简单的数据对了就对了

所以可以自己造比较好手模的数据

代码:

#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define IL inline
#define rep(i,h,t) for(int i=h;i<=t;i++)
#define dep(i,t,h) for(int i=t;i>=h;i--)
#define ll long long
#define me(x) memset(x,0,sizeof(x))
namespace IO{
    char ss[1<<24],*A=ss,*B=ss;
    IL char gc()
    {
        return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++;
    }
    template<class T> void read(T &x)
    {
        rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48);
        while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f; 
    }
    char sr[1<<24],z[20]; int Z,C=-1;
    template<class T>void wer(T x)
    {
        if (x<0) sr[++C]='-',x=-x;
        while (z[++Z]=x%10+48,x/=10);
        while (sr[++C]=z[Z],--Z);
    }
    IL void wer1()
    {
        sr[++C]=' ';
    }
    IL void wer2()
    {
        sr[++C]='
';
    }
    template<class T>IL void maxa(T &x,T y) {if (x<y) x=y;}
    template<class T>IL void mina(T &x,T y) {if (x>y) x=y;} 
    template<class T>IL T MAX(T x,T y){return x>y?x:y;}
    template<class T>IL T MIN(T x,T y){return x<y?x:y;}
};
using namespace IO;
const int mo=1004535809;
const int N=1e6+10;
const int M=1e6;
const int N1=1e7+10;
const int N2=1e7;
int ww[N1],inv[N1],jc[N1],jc2[N1];
int cj[N],nicj[N];
int f[N],g[N],h[N];
void gcd(int x,int y,int &a,int &b)
{
    if (!y)
    {
        a=1; b=0; return;
    }
    gcd(y,x%y,b,a);
    b-=a*(x/y);
}
int ksm(int x,int y)
{
    if (y==0) return(1);
    if (y==1) return(x);
    int k=ksm(x,y/2);
    k=(1ll*k*k)%mo;
    if (y&1) k=(1ll*k*x)%mo;
    return k;
}
IL void js(register int &x,register int y)
{
    x=x+y>=mo?x+y-mo:x+y;
}
int l,n1,r[N],w[N],x[N],y[N],G=3;
void ntt(int *a,int o)
{
    //n n1别写错 
    for (int i=0;i<n1;i++)
      if (i>r[i]) swap(a[i],a[r[i]]);
    for (int i=1;i<n1;i*=2)
    {
        int    wn=ksm(G,(mo-1)/(i*2)); w[0]=1;
        rep(j,1,i-1) w[j]=(1ll*w[j-1]*wn)%mo;
        for(int j=0;j<n1;j+=(i*2))
        {
            rint *x=a+j,*y=a+i+j;
            for (rint k=0;k<i;k++)
            {
                int t=1ll*w[k]*y[k]%mo;
                y[k]=x[k]-t<0?x[k]-t+mo:x[k]-t;
                x[k]=x[k]+t>mo?x[k]+t-mo:x[k]+t;
            }
        }      
    }
    if (o==-1)
    {
        reverse(&a[1],&a[n1]);
        for (int i=0,inv=ksm(n1,mo-2);i<n1;i++)
          a[i]=1ll*a[i]*inv%mo;
    }
}
int t;
void query()
{
    l=0;
    for (n1=1;n1<=t*2;n1<<=1) l++;
    for (int i=0;i<n1;i++) r[i]=(r[i/2]/2)|((i&1)<<(l-1));
    ntt(f,1); ntt(g,1);
    for (int i=0;i<n1;i++) f[i]=(1ll*f[i]*g[i])%mo;
    ntt(f,-1);
}
int main()
{
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    int n,m,s;
    read(n); read(m); read(s);
    rep(i,0,m) read(ww[i]);
    inv[1]=1;
    rep(i,2,N2) inv[i]=(-1ll*(mo/i)*inv[mo%i]%mo+mo)%mo;
    jc2[0]=1; jc[0]=1; cj[0]=1; nicj[0]=1;
    rep(i,1,N2) jc[i]=1ll*jc[i-1]*i%mo,jc2[i]=1ll*jc2[i-1]*inv[i]%mo;
    int x,y;
    gcd(jc[s],mo,x,y); 
    x=(x+mo)%mo;
    rep(i,1,M)
    {  
      cj[i]=1ll*cj[i-1]*jc[s]%mo;
      nicj[i]=1ll*nicj[i-1]*x%mo;
    }
    t=MIN(m,n/s);
    rep(i,0,t)
      if (i%2==0) 
        f[i]=1ll*nicj[i]*jc2[i]%mo;
      else f[i]=(-1ll*nicj[i]*jc2[i]%mo+mo)%mo;
    rep(i,0,t)
      g[i]=1ll*ksm((m-i),n-i*s)*jc2[n-i*s]%mo*jc2[m-i]%mo;
    reverse(g,g+t+1);
    query(); 
    rep(i,0,t) h[i]=(f[t-i]+mo)%mo;
/*    rep(i,0,t)
      rep(j,i,t)
        js(h[j-i],1ll*f[i]*g[j]%mo); */
    int ans=0;
    rep(i,0,t)
    {
        int t1=1ll*ww[i]*jc[n]%mo*nicj[i]%mo*jc[m]%mo*jc2[i]%mo;
        t1=1ll*t1*h[i]%mo;
        js(ans,t1);
    }
    cout<<ans<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/yinwuxiao/p/10029649.html