ABC 267

E (二分、图论)

Problem

给定一个\(n\)个点\(m\)条边的无向图,每个点有一个点权,现在可以进行\(n-1\)次操作,每次操作可以删掉一个图中还存在的点,这次操作的代价是与这个点直接相连的点的点权和。最后这\(n-1\)次操作的最终代价是所有操作中代价最大的那个。问可能的最小最终代价是多少.

\(1\le n,m\le 2\times 10^5\)

Solve

看到最大中的最小,应该考虑二分答案,但如何检查答案。

我们一开始先计算每个点作为一个点删除时的代价\(s_i\),考虑当前二分答案为\(x\),并且这个\(x\)作为我们可选代价的上界,也就是说我们删除一个点的代价不能超过\(x\),那么考虑BFS删点,一开始把所有满足\(s_i\le x\)的点入队,因为不能一开始就选个超过上界代价的点作为初始点删,假设第一轮当前队头的点是\(u\),并且下一个没有访问过的点是\(v\),如果当前\(v\)不是那些可选的初始点,那么\(s_v\gt x\),如果删掉\(u\)的话会使得\(s_v\)减小\(w_u\)的点权,如果此时\(s_v\le x\),那么就把\(v\)入队,否则说明它周围还要继续删点后才能删除它,就不可以入队;如果\(v\)是一个初始点,说明它的\(s_v\le x\),那么可以直接删除,而删除它也只可能会使得那些还未入队的点的点权变小。

也就是说贪心删点,每次之删掉更新后\(s_v\le x\)\(v\),最后检查所有点是否均入队,是,则说明上界可以更小,否则说明上界要更大。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int n,m;
  cin>>n>>m;
  vector<ll>a(n+1);
  for(int i=1;i<=n;i++) cin>>a[i];
  vector<vector<int>>E(n+1);
  for(int i=1;i<=m;i++){
     int u,v;
     cin>>u>>v;
     E[u].push_back(v);
     E[v].push_back(u);
  }
  vector<ll>s(n+1);

  ll l=0,r=1LL<<60;
  auto check=[&](ll x)->bool{
    vector<ll>sum(n+1);
    vector<int>vis(n+1);
    for(int u=1;u<=n;u++)
      for(auto v:E[u]) sum[u]+=a[v];
    queue<pair<ll,int>>q;
    for(int i=1;i<=n;i++)
      if(sum[i]<=x) q.push({sum[i],i});
    while(q.size()){
      auto t=q.front();
      q.pop();
      int w=t.first,u=t.second;
      if(vis[u]) continue;
      vis[u]=1;
      for(auto v:E[u]){
        if(sum[v]<=x) continue;
        sum[v]-=a[u];
        if(sum[v]<=x && !vis[v]){
          q.push({sum[v],v});
        }
      }
    }
    for(int i=1;i<=n;i++) if(!vis[i]) return false;
    return true;
  };
  ll ans=-1;
  while(l<=r){
    ll mid=(l+r)>>1;
    if(check(mid)) ans=mid,r=mid-1;
    else l=mid+1;
  }
  cout<<ans<<'\n';
}

F.Exactly K Steps(树的直径、性质、图论)

Problem

给定一个\(n\)个点的树,树上两个点之间的路径为两个点直接的边数,现在有\(q\)次询问,每次询问给定一组\((u_i,k_i)\),要求输出任意一个于\(u_i\)之间距离为\(k_i\)的点

\(1\le n,q\le 10^5\)

Solve

首先,可能不能对于每个询问一个一个地DFS去找,时间复杂度\(O(n^2)\)不可以接受

所以考虑把询问离线下来。然后我们考虑树的直径的两个端点\(L\)\(R\),对于一个对点任意一对点\((u,v)\),有\(d(u,v)\le \max(d(u,L),d(u,R))\),考虑证明:如果存在一对点\((A,B)\),使得\(d(A,B)\gt \max(d(A,L),d(A,R))\),令\(a\)\(L\)\(R\)在以\(A\)为根时的LCA,\(b\)同样的定义,则\(d(A,a)+d(a,b)+d(b,B)\ge d(A,B)\gt d(A,R)=d(A,a)+d(a,R)\),因此\(d(a,b)+d(b,B)\gt d(a,R)\),所以\(d(L,B)=d(L,a)+d(a,b)+d(b,B)\gt d(L,a)+d(a,R)=d(L,R)\),与\(d(L,R)\)是最长路径矛盾。

所以我们直接分别以\(L\)\(R\)作根进行一遍DFS,然后记录一下递归栈记录答案即可。因为对\(u_i\),与它距离为\(k_i\)的点\(v_i\)要么在\(L\rightarrow u_i\)的路径上,要么在\(R\rightarrow v_i\)的路径上。

Code

#include <bits/stdc++.h>
using namespace std;
int main(){
  //ios::sync_with_stdio(false);
  //cin.tie(nullptr);
  int n;
  cin>>n;
  vector<vector<int>>E(n+1);
  for(int i=1;i<n;i++){
    int u,v;
    cin>>u>>v;
    E[u].push_back(v);
    E[v].push_back(u);
  }
  int q;
  cin>>q;
  vector<vector<pair<int,int>>>qry(n+1);
  for(int i=1;i<=q;i++){
    int u,k;
    cin>>u>>k;
    qry[u].push_back({i,k});
  }
  auto bfs=[&](int u)->int{
       queue<int>q;
       vector<int>dist(n+1,n);
       dist[u]=0;
       q.push(u);
       while(q.size()){
         int u=q.front();
         q.pop();
         for(auto v:E[u]){
            if(dist[v]>dist[u]+1){
              dist[v]=dist[u]+1;
              q.push(v);
            }
         }
       }
       int p=1;
       for(int i=2;i<=n;i++){
          if(dist[p]<dist[i]) p=i;
       }
       return p;
  };
  int L=bfs(1),R=bfs(L);
  vector<int>ans(q+1,-1);
  vector<int>path;
  auto dfs=[&](auto self,int u,int fa)->void{
    for(auto [i,k]:qry[u]){
        if(k<=(int)path.size()){
          ans[i]=path[(int)path.size()-k];
        }
      }
        path.push_back(u);
        for(auto v:E[u]){
          if(v!=fa) self(self,v,u);
        }
        path.pop_back();

  };
  dfs(dfs,L,L);
  dfs(dfs,R,R);
  for(int i=1;i<=q;i++) cout<<ans[i]<<"\n";
  return 0;

}

G.Increasing K Times (DP)

Problem

给定一个长度为\(N\)的序列\(A\),找到有多少个长度为\(N\)的排列\(P\),满足存在恰好\(K\)个整数\(i\)满足\(A_{P_i}\lt A_{P_{i+1}}\)

\(1\le N\le 5000\)\(0\le K\le N-1\)\(1\le A_i\le N\)

Solve

考虑DP,定义\(dp_{i,j}\)表示前\(i\)个数,有\(j\)个数满足条件的个数。考虑把\(A_i\)从小到大依次加入,假设当前加入了\(m\)个数\((a_1,a_2,\cdots,a_m)\),那么加入第\(m+1\)个数\(x\)的时候,要么$a_i\lt a_{i+1} \(的个数\)+1$,要么不变

  • 如果是\(+1\)的情况,说明把\(x\)加入了满足\(x\gt a_i\ge a_{i+1}\)\(a_i\)\(a_{i+1}\)之间,对于有多少\(a_i\ge a_{i+1}\)可以通过有多少\(a_i\lt a_{i+1}\)来计算
  • 如果是不变的情况,说明把前面\(a_i=x\)处且满足\(a_i\ge a_{i+1}\)\(a_i\)替换成\(x\)

Code

#include <bits/stdc++.h>
#define  ll long long
using namespace std;
const int mod=998244353;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int n,k;
  cin>>n>>k;
  k+=1;
  vector<int>cnt(n+1);
  for(int i=0;i<n;i++){
    int x;
    cin>>x;
    cnt[x]++;
  }
  vector<ll>dp(k+1,0);
  dp[0]=1;
  int put=0,same=0;
  for(int i=0;i<n;i++){
      while(cnt[put]==0){
        put+=1;
        same=0;
      }
      vector<ll> nxt(k+1,0);
      for(int j=0;j<=k;j++){
         ll val=dp[j];
         nxt[j]=(nxt[j]+val*(same+j)%mod)%mod; //不变
         if(j<k)
          nxt[j+1]=(nxt[j+1]+val*(i+1-j-same+mod)%mod)%mod; //增加
      }
      dp=move(nxt);
      cnt[put]-=1;
      same+=1;
  }
  cout<<dp[k]<<'\n';
  return 0;
}

Ex - Odd Sum(生成函数、多项式)

Problem

给定一个长度为\(N\)的序列\(\{A\}\),问有多少种不同的方案,可以从这个序列中选择奇数个数使它们的和为\(M\)

\(1\le N\le 10^5\)\(1\le M\le 10^6\) \(1\le A_i\le 10\)

Solve

构造生成函数\(F_1=\prod_{i=1}^N(1+A_i)\)\(F_2=\prod_{i=1}^N(1-A_i)\)

\([x^M]F_1\)就表示多少种选数方案使得它们的和为\(M\),不考虑选数的奇偶性,即\(odd+even\)

\([x^M]F_2\)也表示多少种选数方案使得它们的和为\(M\),不过这里如果选数的个数是偶数,那么就加上,否则就减去,即\(even-odd\)

那么选数个数为奇数且和为\(M\)的方案数就是\(\frac{[x^M]F_1-[x^M]F_2}{2}\),即\(odd=\frac{odd+even-(even-odd)}{2}\)

Code

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;

const int N = 5e6+10;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809
//mod=950009857 g=7
const int mod =  998244353;


template <typename T>void read(T &x)
{
    x = 0;
    register int f = 1;
    register char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
    x *= f;
}

ll qpow(ll a, ll b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}


namespace Poly
{
    #define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
    #define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
    #define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
    #define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了

    typedef vector<ll> poly;
    const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
    //一般模数的原根为 2 3 5 7 10 6
    const int inv_G = qpow(G, mod - 2);
    int RR[N], deer[2][21][N], inv[N];

    void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
        for(int p = 1; p <= t; ++ p) {
            int buf1 = qpow(G, (mod - 1) / (1 << p));
            int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
            deer[0][p][0] = deer[1][p][0] = 1;
            for(int i = 1; i < (1 << p); ++ i) {
                deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
                deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
            }
        }
        inv[1] = 1;
        for(int i = 2; i <= (1 << t); ++ i)
            inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }

    inline int NTT_init(int n) {//快速数论变换预处理
        int limit = 1, L = 0;
        while(limit <= n) limit <<= 1, L ++ ;
        for(int i = 0; i < limit; ++ i)
            RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
        return limit;
    }

    inline void NTT(poly &A, int type, int limit) {//快速数论变换
        A.resize(limit);
        for(int i = 0; i < limit; ++ i)
            if(i < RR[i])
                swap(A[i], A[RR[i]]);
        for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
            int len = mid >> 1;
            for(int pos = 0; pos < limit; pos += mid) {
                int *wn = deer[type][j];
                for(int i = pos; i < pos + len; ++ i, ++ wn) {
                    int tmp = 1ll * (*wn) * A[i + len] % mod;
                    A[i + len] = ck(A[i] - tmp + mod);
                    A[i] = ck(A[i] + tmp);
                }
            }
        }
        if(type == 0) {
            for(int i = 0; i < limit; ++ i)
                A[i] = 1ll * A[i] * inv[limit] % mod;
        }
    }
    inline poly poly_mul(poly A, poly B) {//多项式乘法
        int deg = A.size() + B.size() - 1;
        int limit = NTT_init(deg);
        poly C(limit);
        NTT(A, 1, limit);
        NTT(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            C[i] = 1ll * A[i] * B[i] % mod;
        NTT(C, 0, limit);
        C.resize(deg);
        return C;
    }
    //多项式牛顿迭代:求g(f(x))=0mod(x^n)中的f(x)
}



int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    Poly::init(20);
    int n,m;
    cin>>n>>m;
    vector<Poly::poly>f1(n+1),f2(n+1);
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        f1[i].resize(x+1);
        f2[i].resize(x+1);
        f1[i][x]=1,f1[i][0]=1;
        f2[i][x]=mod-1,f2[i][0]=1;
    }
    auto CDQ=[&](auto self,int l,int r,vector<Poly::poly>&f)->Poly::poly{
        if(l==r) return f[l];
        int mid=(l+r)>>1;
        return Poly::poly_mul(self(self,l,mid,f),self(self,mid+1,r,f));
    };
    auto res1=CDQ(CDQ,1,n,f1);
    auto res2=CDQ(CDQ,1,n,f2);
    res1.resize(m+1);
    res2.resize(m+1);
    ll ans=1LL*(res1[m]-res2[m]+mod)%mod*qpow(2,mod-2)%mod;
    cout<<ans<<'\n';
}

posted @ 2022-09-06 09:42  Arashimu  阅读(107)  评论(0编辑  收藏  举报