Atcoder 235
作者:互联网
Atcoder 235
\(E.\) MST + 1
题意(唯一一次10分钟之内一次AC的E题,太菜了)
给定一个有\(N\)个点,\(M\)条边的无向连通图\(G\),可能有重边和自环,现在有\(Q\)个询问,每次询问给定一个条边\((u_i,v_i,w_i)\),问把条边加入\(G\)中,这条边是否会出现在\(G^{'}\)的最小生成树中,保证\(w_i\)在原图的边权中没有出现过。
\(2\le N\le 2\times 10^5\) \(N-1\le M<\le 2\times 10^5\) \(1\le a_i,b_i\le N\) \(1\le c\le 10^9\) (\(c_i!=c_j\))
\(1\le Q \le 2\times 10^9\) \(1\le u_i,v_i\le N\) \(1\le w_i\le 10^9\)
思路
克鲁斯卡尔重构树+倍增
#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=2e5+10;
int fa[N*2];
int var[N*2];
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
struct edges
{
int u,v,w;
bool operator <(const edges &t)const
{
return w<t.w;
}
}e[N];
vector<int>E[2*N];
int f[2*N][22],d[2*N];
void dfs(int u,int father,int depth)
{
f[u][0]=father;
d[u]=depth;
for(int i=1;i<=20;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(auto v:E[u])
{
if(v==father) continue;
dfs(v,u,depth+1);
}
}
int LCA(int u,int v)
{
if(d[u]<d[v]) swap(u,v);
for(int i=20;i>=0;i--)
if(d[f[u][i]]>=d[v]) u=f[u][i];
if(u==v) return u;
for(int i=20;i>=0;i--)
if(f[u][i]!=f[v][i])
{
u=f[u][i];
v=f[v][i];
}
return f[u][0];
}
//重构树上跳一跳
int main()
{
int n,m,q;
rd(n),rd(m),rd(q);
for(int i=1;i<=2*n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int u,v,w;
rd(u),rd(v),rd(w);
e[i]={u,v,w};
}
int cnt=n;
sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
int x=e[i].u,y=e[i].v,w=e[i].w;
if(x==y) continue;
int fx=find(x),fy=find(y);
if(fx!=fy)
{
cnt++;
E[cnt].pb(fx);
E[cnt].pb(fy);
fa[fx]=fa[fy]=cnt;
var[cnt]=w;
}
}
dfs(cnt,cnt,1);
while(q--)
{
int u,v,w;
rd(u),rd(v),rd(w);
int lca=LCA(u,v);
if(var[lca]>w) puts("Yes");
else puts("No");
}
return 0;
}
\(F.\)Variety of Digits
题意
给定\(M\)个数字\(C_i\),请求出从\(1\)到\(N\)中包含所有\(C_1,C_2,...,C_M\)的数字的和,答案对\(998244353\)取模
\(1\le N\le 10^{10^4}\) \(1\le M\le 10\) \(0\le C_i\le 9\)
Sol
数位DP(一直不会,唉)
定义\(dp[2][1e4][2][(1<<10)+10]\)
1.第一维\(0\)表示计数,\(1\)表示求和
2.第二维表示当前是第几位
3.第三维\(0\)表示当前这位有前一位的限制,也就是前一位到了自身的最大数值,当前位的枚举上限变为\(N\)这位的数,\(1\)则表示没有前一位的限制
4.第4维表示当前所用到的数字的集合,由于最多10个数字,所以可以用二进制来描述哪些数字出现过,所以第\(4\)维是\(1<<10\)
由于每次转移只和前一位有关,所以可以用滚动数组优化掉第2维,这里采用新建一个数组的方式,所以最终的dp为\(dp[2][2][(1<<10)+10]\)
这里只讲和的转移
一般数位dp都是从高位开始转移
假设有3位数并且枚举到第3位
假设前2位出现的数字的集合是\(\{1,2\}\)
说明前两位是\(12\)和\(21\),假设第\(3\)位枚举到了\(3\)且没有限制
\(123+213=(12+21)*10+2*3\)
那么第\(3\)位和的转移就是\(dp[1][1][111]+=(12+21)*10+2*3\)
乘\(10\)的部分就是集合为\(s=11\)的前一位的数字和,乘\(3\)前面的部分就是集合为\(s=11\)的前一位的数量
#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=1e5+10;
int v[N];
ll dp[2][2][(1<<10)+10],f[2][2][(1<<10)+10];
//第一维0表示计数,1表示求和
//第二维表示是否有limit,即是否有前一位是最大值的限制,0表示有,1表示没有
//第三位表示用了哪些数的集合
int main()
{
string s;
cin>>s;
int n=s.size();
for(int i=0;i<n;i++) v[i]=s[n-1-i]-'0';
int S=0;
int m;
rd(m);
for(int i=1;i<=m;i++)
{
int c;
rd(c);
S|=(1<<c);
}
for(int i=n-1;i>=0;i--) //从高位开始枚举
{
//枚举当前位
for(int x=1;x<=(i==n-1?v[i]:9);x++)
{
auto &f1=f[0][i<n-1||x<v[i]][1<<x];
auto &f2=f[1][i<n-1||x<v[i]][1<<x];
f1=(f1+1)%mod;
f2=(f2+x)%mod;
}
for(int limit=0;limit<2;limit++)//枚举限制
for(int s=0;s<(1<<10);s++)//枚举集合
for(int x=0;x<=(limit?9:v[i]);x++) //如果前一位是最大数了,那么当前这位不能超过当前位的最大值
{
auto &f1=f[0][limit||x<v[i]][s|(1<<x)];
auto &f2=f[1][limit||x<v[i]][s|(1<<x)];
auto &dp1=dp[0][limit][s];
auto &dp2=dp[1][limit][s];
f1=(f1+dp1)%mod;
f2=(f2+dp2*10%mod+dp1*x%mod)%mod;
}
memcpy(dp,f,sizeof f);
memset(f,0,sizeof f);
}
ll ans=0;
for(int i=0;i<2;i++)
for(int s=0;s<(1<<10);s++)
if((s&S)==S) ans=(ans+dp[1][i][s])%mod;
cout<<ans<<'\n';
return 0;
}
\(G.\)Gardens
题意
果农有\(A\)个苹果种子,有\(B\)个香蕉种子,有\(C\)个樱桃种子,他有\(N\)个花园
现在他要把这些种子种在花园里,但有一下条件
- 每个花园至少种1个种子
- 不允许有相同的种子种在同一个花园里
- 不必用完所有的种子
请求出所有可能的种植方案数
\(1\le N\le 5\times 10^5\) \(\le A,B,C\le N\)
Sol
考虑容斥
假设允许花园为空并且用完所有的种子
那么方案数就是\(\sum_{i=0}^NC_n^iC_{n-i}^AC_{n-i}^BC_{n-i}^C\),其中\(C_n^i\)为选择的空的花园方案
那么依据容斥原理,不允许花园为空就是\(\sum_{i=0}^N(-1)^iC_n^iC_{n-i}^AC_{n-i}^BC_{n-i}^C\)
考虑条件三,由于可以不必用完所用的种子,假设当前位苹果种子,那么选完\(i\)个空的花园后,苹果种子的方案应该为\(\sum_{k=0}^AC_{n-i}^j\)
同理其它两个也是,所以答案应该为\(\sum_{i=0}^N(-1)^iC_n^i\sum_{k=0}^AC_{n-i}^j\sum_{k=0}^BC_{n-i}^j\sum_{k=0}^CC_{n-i}^j\)
后面的和考虑用前缀和优化
由于\(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\)
所以有\(\sum_{i=0}^mC_{n+1}^i=\sum_{i=0}^{m-1}C_{n}^i+\sum_{i=0}^mC_{n}^i=2\sum_{i=0}^mC_{n}^i-C_n^m\)
可以令\(sa\)为苹果种子的前缀和,那么更新时\(sa=2*sa-C_{n-i}^A\)
#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=5e6+10;
int n,a,b,c;
ll fac[N],infac[N],inv[N];
ll qmi(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
ll C(int x,int y)
{
if(y<0||x<y) return 0;
else return fac[x]*infac[y]%mod*infac[x-y]%mod;
}
int main()
{
rd(n),rd(a),rd(b),rd(c);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
infac[n]=qmi(fac[n],mod-2);
for(int i=n;i>=1;i--)
infac[i-1]=infac[i]*i%mod;
ll ans=0;
ll sa=1,sb=1,sc=1;
//C(n,m)=C(n-1,m-1)+C(n-1,m);
//sumC(n+1,0....,m+1)=sumC(n,0.....,m)+sumC(n,0....,m+1)=2*sumC(n,0....m+1)-C(n,m+1)
for(int i=0;i<=n;i++)
{
if((n-i)%2) ans=(ans-C(n,i)*sa%mod*sb%mod*sc%mod+mod)%mod;
else ans=(ans+C(n,i)*sa%mod*sb%mod*sc%mod)%mod;
sa=(sa*2%mod-C(i,a)+mod)%mod;
sb=(sb*2%mod-C(i,b)+mod)%mod;
sc=(sc*2%mod-C(i,c)+mod)%mod;
}
cout<<ans<<'\n';
return 0;
}
\(Ex.\)Painting Weighted Graph
题意
标签:Atcoder,le,10,int,ll,235,sum,define 来源: https://www.cnblogs.com/Arashimu0x7f/p/15968696.html