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\)种硬币。
如果第\(N\)种硬币不找钱,则\(f[N][X]=f[N-1][X\%A[N]]+X/A[N]\)
如果找钱,则\(f[N][X]=f[N-1][A[N]-X\%A[N]]+X/A[N]+1\),假设\(kA[N]\lt X,(k+1)A[N]>X\),那么需要找钱数位\((k+1)A[N]-X=A[N]-(X-kA[N])=A[N]-X\%A[N]\)
#include <bits/stdc++.h>
#define ll long long
#define inf 0x7f7f7f7fll
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> 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 <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=65;
ll A[N];
map<ll,ll>f[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<<dfs(n,x)<<'\n';
return 0;
}
\(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<i+1\),也等价于\(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 <bits/stdc++.h>
#define ll long long
#define inf 1e18
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> 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 <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> void chkMin(T &x, T y) { if (y < x) x = y; }
const int N=2e5+10;
int n,m;
ll tr[N];
vector<int>v;
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<t.a;
}
}P[N];
int main()
{
rd(n);
for(int i=1;i<=n;i++) rd(P[i].a);
for(int i=1;i<=n;i++) rd(P[i].b),v.push_back(P[i].b);
sort(P+1,P+1+n);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
m=v.size();
ll ans=0;
for(int i=1;i<=n;i++)
{
int p=lower_bound(v.begin(),v.end(),P[i].b)-v.begin()+1;
add(p);
int cnt=1;
while(P[i+1].a==P[i].a&&P[i+1].b==P[i].b)
{
i++;
add(p);
cnt++;
}
ans+=(sum(m)-sum(p-1))*cnt;
}
cout<<ans<<endl;
return 0;
}
\(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 <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mod 998244353
#define pb push_back
using namespace std;
template <typename T> 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 <typename T> void chkMax(T &x, T y) { if (y > x) x = y; }
template <typename T> 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);
queue<int>q;
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]<d[u]+c)
{
d[v]=d[u]+c;
pre[v]=i;
incf[v]=min(incf[u],f);
if(!st[v])
{
st[v]=1;
q.push(v);
}
}
}
}
return incf[T]>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<<ans-cost<<endl;
return 0;
}
\(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 <bits/stdc++.h>
#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 <typename T>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;
}
vector<ll>A[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<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;
}
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<<ans<<endl;
return 0;
}