Atcoder 231


Atcoder 231

\(E.\)Minimal payments (dp)

题意

\(N\)种面值的硬币\(A_1,A_2,...,A_N\),现在要用这\(N\)中硬币购买一种价值为\(X\)的商品,问这个购买过程中最少交易的总硬币数是多少。

就是求付钱的硬币数和找钱的硬币数总和最小。

\(1\le N\le 60\) \(1=A_1\lt ...\lt A_N\le 10^{18}\) \(1\le X\le 10^{18}\)

Sol

记忆化搜索,定义\(f[N][X]\)为当前交易的钱数和使用到了第\(N\)种硬币。

#include 
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  void chkMax(T &x, T y) { if (y > x) x = y; }
template  void chkMin(T &x, T y) { if (y < x) x = y; }

const int N=65;
ll A[N];
mapf[105];
ll dfs(int n,ll x)
{
    if(n==1) return x;
    if(f[n].find(x)!=f[n].end())
        return f[n][x];
    f[n][x]=dfs(n-1,x%A[n])+x/A[n];   //不找钱,继续交易
    if(x%A[n]) f[n][x]=min(f[n][x],dfs(n-1,A[n]-x%A[n])+x/A[n]+1); //找零钱
    return f[n][x];
}
int main()
{
    int n;ll x;
    rd(n),rd(x);
    for(int i=1;i<=n;i++) rd(A[i]);
    cout<

\(F.\)Jealous Two(数据结构)

题意

给定两个长度为\(N\)的序列\(A=(A_1,A_2,...,A_N)\)\(B=(B_1,B_2,...,B_N)\),求有多少对\((i,j)\)\(st.\) \(A[i]\ge A[j]\) 并且 \(B[i]\le B[j]\)

\(1\le N \le 2\times 10^5\) \(1\le A_i,B_i\le 10^9\)

Sol

很明显的二维偏序问题,做法很明显就是离散化和树状数组,但很多细节处理起来很麻烦,首先很容易想到将二元对\((A_i,B_i)\)按照第一位从小到大排序,第二位按照从大到小排序,那么从\(i:1->N\)遍历的时候,一定满足\(i,也等价于\(A[i]\le A[i+1]\),所以就求排完序后有多少逆序对。但由于可能存在一段连续的\(A_i,B_i\)均相同的情况,如果不特殊处理,那么假设\(1\ 2\ 3\)\(3\)个下标的\(A_i,B_i\)均相同,则在用树状数组统计时,只会统计到\((1,1)(2,2)(2,1)(3,3)(3,1)(3,2)\),但实际上还会有到\((1,2)(1,3)(2,3)\),所以还要统计每个连续段有多少相同的下标,然后用树状数组统计出有多少个逆序对,再乘以这些下标总数即可。

#include 
#define ll long long
#define inf 1e18
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  void chkMax(T &x, T y) { if (y > x) x = y; }
template  void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=2e5+10;
int n,m;
ll tr[N];
vectorv;
int lowbit(int x){return x&-x;}
void add(int p)
{
    for(;p<=m;p+=lowbit(p))
        tr[p]++;
}
ll sum(int p)
{
    ll res=0;
    for(;p;p-=lowbit(p))
        res+=tr[p];
    return res;
}
struct node
{
    int a,b;
    bool operator < (const node &t) const
    {
        if(a==t.a) return b>t.b;
        else return a

\(H.\) Minimum Coloring(网络流)

给定一个\(H\times W\)的网格图,里面有\(N\)个白色棋子,第\(i\)个用三个参数表示\((a_i,b_i,c_i)\)\(a_i,b_i\)分别表示棋子的横纵坐标,\(c_i\)表示棋子染成黑色的花费,问最少需要花费多少,可以使得每一行每一都有黑色的棋子

  • \(1≤H,W≤10^3\)
  • \(1\le N\le 10^3\)
  • \(1 \leq C_i≤10^9\)

Sol
先假定将每个白色棋子都染成黑色,那么问题就是求最多可以染多少个黑色棋子变为白色,使得每行每列都有一个黑色棋子,那么问题就编程求最大费用最大流问题了。

建图方式:

  • 记录每一行每一列有多少个棋子分别记为\(dh[i]\)\(dw[i]\),并且建立两个超级源点\(S=0\)和汇点\(T=h+w+1\)
  • 每个棋子\((a_1,b_i,c_i)\),连一条\(a_i\)\(b_i+h\)容量为\(1\)费用为\(c_i\)的边
  • 源点\(S\)向每行连一条\(S\)\(i\)容量为\(dh[i]-1\)费用为\(0\)的边,减\(1\)是因为每一行要至少要有1个棋子,在保证这样的前提下,尽量多的流流量。
  • 每一列\(i+h\)向汇点\(T\)连一条容量为\(dw[i]-1\)费用为\(0\)的边,减\(1\)是因为每一列要至少要有1个棋子。
  • 然后跑最大费用流得到\(cost\)
  • 最终答案就是\(\sum_{i=1}^nc_i-cost\)
#include 
#define ll long long
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;

template  void rd (T &x)
{
    x=0;int f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=x*10+(s^48),s=getchar();
    x*=f;
}
template  void chkMax(T &x, T y) { if (y > x) x = y; }
template  void chkMin(T &x, T y) { if (y < x) x = y; }

const int N=2e3+10,M=6e3+10,inf=2e9;
int S,T,cnt;
int head[N],st[N];
ll d[N],incf[N],pre[N];
struct edges
{
    int v,nxt;
    ll f,c;
}e[M];
void add(int u,int v,int f,int c)
{
     e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,e[cnt].c=c,head[u]=cnt++;
     e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,e[cnt].c=-c,head[v]=cnt++;
}
bool spfa()
{
    memset(d,-0x3f,sizeof d);
    memset(incf,0,sizeof incf);
    queueq;
    q.push(S);
    d[S]=0,incf[S]=inf;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=0;
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].v;
            ll f=e[i].f,c=e[i].c;
            if(f&&d[v]0&&d[T]>0;
}
void EK(ll &flow,ll &cost)
{
    flow=cost=0;
    while(spfa())
    {
        ll t=incf[T];
        flow+=t,cost+=t*d[T];
        for(int i=T;i!=S;i=e[pre[i]^1].v)
        {
            e[pre[i]].f-=t,e[pre[i]^1].f+=t;
        }
    }
}

int dh[N],dw[N];
int main()
{
    memset(head,-1,sizeof head);
    
    int h,w,n;
    rd(h),rd(w),rd(n);
    S=0,T=h+w+1;
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
      int x,y,c;
      rd(x),rd(y),rd(c);
      add(x,y+h,1,c);
      ans+=c;
      dh[x]++,dw[y]++;
    }
    for(int i=1;i<=h;i++) add(S,i,dh[i]-1,0);
    for(int i=h+1;i<=h+w;i++) add(i,T,dw[i-h]-1,0);
    ll flow,cost;
    EK(flow,cost);
    cout<

\(G.Balls in Boxes\)(期望,数学,多项式)

题意

\(N\)个盒子,每个盒子一开始有\(A_i\)个球,可以进行\(K\)次操作,每次操作可以随机选一个盒子加一个球,\(K\)次操作后每个盒子有\(B_i\)个球,令\(S=\prod_{i=1}^nB_i\),求\(E(S)\),答案对\(998244353\)取模

\(1\le N\le 1000\) \(1\le K\le 10^9\) \(1\le A_i\le10^9\)

Sol

\(X_i\)为第\(i\)个盒子被选中的随机变量。则\(E(S)=E(\prod_{i=1}^n(A_i+X_i))\)

\(n=2\)为例子,\(E(S)=E(A_1A_2)+E(A_1X_2)+E(A_2X_1)+E(X_1X_2)\)

由于\(A_1,A_2\)是常数,所以\(E(S)=A_1A_2+A_1E(X_2)+A_2E(X_1)+E(X_1X_2)\)

\(n=3\)时,\(E(S)=E(X_1X_2X_3)+A_1E(X_2X_3)+A_2E(X_1X_3)+A_3E(X_1X_2)+A_1A_2E(X_3)+A_1A_3E(X_2)+A_2A_3E(X_1)+A_1A_2A_3\)

观察前面的系数为\(1+A_1 + A_2+ A_3+ A_1A_2 + A_1A_3 + A_2A_3+ A_1A_2A_3=(1+A_1)(1+A_2)(1+A_3)\)

\(S=\prod_{i=1}^n(1+A_ix)\)\(S(i)\)表示第\(i\)项的系数

同时我们还可以发现\(E(\prod_{j=1}^mX_{j_i})=E(\prod_{i=1}^mX_i)\),表示\(E\)中如果是个数相同的\(X_i\)相乘,那么他们的值总相等,于是这部分可以提取出来

那么\(E(S)=E(X_1X_2X_3)+(A_1+A_2+A_3)E(X_1X_2)+(A_1A_2+A_1A_3+A_2A_3)E(X_1)+A_1A_2A_3\)

? \(=S(0)E(X_1X_2X_3)+S(1)E(X_1X_2)+S(2)E(X_1)+S(3)E(1)\)

可以归纳总结出\(E(\prod_{i+1}^n(A_i+X_i))=\sum_{i=0}^nS(i)E(\prod_{j=1}^{n-i}X_j)\)

于是答案就很好求了,前面的\(S(i)\)可以先用\(CDQ\)分治或启发式合并利用\(NTT\)快速求\(O(n(logn)^2)\)

后面一部分考虑随机变量\(X_{i,j}\)表示第\(i\)个盒子在第\(j\)次操作是否被选中\((0/1)\)。则\(X_i=\sum_{j=1}^kX_{i,j}\)

\(E(\prod_{i=1}^nX_i)=E(\prod_{i=1}^n\sum_{j=1}^kX_{i,j})=\sum_{j_1,j_2,...,j_n}E(\prod_{i=1}^nX_{i,j_i})\)

后面的期望当且仅当\(X_{i,j_i}\)均为\(1\)的时候得1,否则均为0,而全部相等就每个\(i\)都被取到,取到\(i\)的概率为\(\frac{1}{N}\)\(n\)个均被取到,则\((\frac{1}{N})^n\)

因此 \(E(\prod_{i=1}^nX_i)=(\prod_{i=1}^n(K+1-i))(\frac{1}{N})^n=\frac{K!}{(K-n)!}(\frac{1}{N})^n=A_K^n(\frac{1}{N})^n\)

//#pragma GCC optimize(2)
#include 
#define ll long long
#define pb push_back
using namespace std;

const int N = 1100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809 
const int M=18e5;
const int mod=998244353;
template void rd(T &x)
{
    x = 0;
     int f = 1;
     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, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}

vectorA[N];
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 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;
    }

    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;
    }

    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;
    }
    //多个多项式相乘CDQ或者利用优先队列启发式合并
    inline poly CDQ(int l,int r)
    {
      if(l==r)
      { 
        return A[l];
      }
      int mid=l+r>>1;
      poly L=CDQ(l,mid);
      poly R=CDQ(mid+1,r);
      return poly_mul(L,R);  
    }
}

using namespace Poly;
int n, k;
ll E[N];
int main()
{
    //freopen("in.in","r",stdin);
    init(20);//2^21 = 2,097,152,根据题目数据多项式项数的大小自由调整,注意大小需要跟deer数组同步(21+1=22)
    int n,k;
    rd(n),rd(k);
    for(int i=1;i<=n;i++)
    {
        int x;
        rd(x);
        A[i].pb(1);
        A[i].pb(x);
    }
    poly s=CDQ(1,n);
    int inv=qpow(n,mod-2);
    E[0]=1;
    for(int i=1;i<=n;i++)
        E[i]=E[i-1]*inv%mod*(k-i+1+mod)%mod;
    ll ans=0;
    for(int i=0;i<=n;i++)
        ans=(ans+s[i]*E[n-i]%mod)%mod;
    cout<

相关