AtCoder Regular Contest 116(C~E)
C - Multiple Sequences
题意:给一个n,m要求构造一个长为n的数组,使得每个后一位是前一位的倍数,并且所有值小于等于m,求方案数。
题解:
如何不重不漏的计算所有方案,我考虑枚举最后一位的值是多少,并求和。
那么问题就变成的已知最后一位,求有多少种方案使数组的前一位是后一位的倍数。
我们设dp[i] [j]表示长度为i时,第i位为j时的方案数。
易知dp[i] [j]可以由所有dp[i-1] [x] (x为j的因子)转移,那么时间复杂度就为n*m。
直接超时了。
考虑如何优化,很明显一个数被质因数分解后最多的大概在20个因子左右,那么一个数组要求每一位是前一位的倍数,其实最多就增长了20多次。
想当于,把因子视为板插入大小为n的数组里,每个板之间数都是一样大的,在
板交界处翻倍,经典隔板法求方案。
所以我们把dp[i] [j]表示长度为i,第i位j时的方案数(与上不同的是,这里前一位必须比j小,那么第一维就只要开20了)。
#include<iostream>
using namespace std;
#define ll long long
const ll mod=998244353;
const ll N=2e5+7;
ll n,m;
ll f[27][N];
ll fac[N];
ll qpow(ll x,ll n) {
ll res=1;
for (;n;n>>=1,x=x*x%mod){
if (n&1)res=res*x%mod;
}
return res;
}
ll inv(ll a){
return qpow(a,mod-2)%mod;
}
void solve(){
fac[0]=1;
for(int i=1;i<N;i++) {
fac[i]=(fac[i-1]*i)%mod;
}
}
ll comb(ll n,ll k){
if(k>n)return 0;
if(k==1)return n;
return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
int main(){
solve();
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
f[1][i]=1;
}
for(int i=1;i<=20;i++){
for(int j=1;j<=m;j++){
for(int k=2;k*j<=m;k++){
f[i+1][k*j]+=f[i][j];
f[i+1][k*j]%=mod;
}
}
}
ll ans=0;
for(int i=1;i<=20;i++){
for(int j=1;j<=m;j++){
ans=(ans+f[i][j]*comb(n-1,i-1)%mod)%mod;
}
}
cout<<ans<<"
";
}
D - I Wanna Win The Game
题意:给一个n,m要求构造一个长为n的数组,使得所有数之和为m,且所有数的异或和为0求方案数。
题解:
首先思考怎么时异或和为0,显然将数组中所有数按二进制拆开,如果所有二进制数的个数之和为偶数的话,那么数组的异或和就为0。
所以我们其实是要将m个小球放入n个盒子里,同时放的时候要按二进制放并且只能放偶数个(就先放偶数个1,再放偶数个2,再放偶数个4)有点类似于硬币问题求方案数同时在加个组合数学。
#include<iostream>
using namespace std;
#define ll long long
const ll N=5007;
const ll mod=998244353;
ll fac[N];
ll pow(ll x,ll n,ll mod){
ll res=1;
while(n>0){
if(n%2==1){
res=res*x%mod;
}
x=x*x%mod;
n>>=1;
}
return res;
}
ll inv(ll a){
return pow(a,mod-2,mod);
}
void solve(){
fac[0]=1;
for(int i=1;i<N;i++) {
fac[i]=(fac[i-1]*i)%mod;
}
}
ll comb(ll n,ll k){
if(k>n)return 0;
if(k==1)return n;
return (fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod);
}
ll er[20],c[5007][5007];
ll n,m;
ll f[5007][5007];
void init(){
er[0]=1;
for(int i=1;i<=20;i++){
er[i]=er[i-1]*2;
}
for(int i=0;i<=n;i++){
c[n][i]=comb(n,i);
}
}
int main(){
solve();
scanf("%lld%lld",&n,&m);
init();
f[0][0]=1;
for(int i=1;i<=20;i++){
if(er[i-1]>m){
printf("%lld
",f[i-1][m]);
break;
}
for(int j=0;j<=n;j+=2){
ll lin=er[i-1]*j;
if(lin>m)break;
for(int k=lin;k<=m;k++){
f[i][k]+=f[i-1][k-lin]*c[n][j]%mod;
f[i][k]%=mod;
}
}
}
return 0;
}
E - Spread of Information
题意:给一个树,要你最多放k个点,以这k个点作为源点,bfs能到的最远距离最短。
题解:
我们直接二分答案,那么题目就转变成了:
1、最远距离为x,使用k个点是否够用。
我们可以在进行转换,若我们能求出:
2、使最远距离为x,最少可使用y个点。
通过比较y和x就可以判断x是否合法。
那么对于问题2,这是一个很经典的树形dp题,与 https://www.luogu.com.cn/problem/P2016 类似。只是上面题是距离为1的边,此题是距离为x的点。
但可以启发我们做此题,具体操作就是用两个数组,一个表示其子树最远为被感染的点的距离,一个表示子树中最近的被覆盖点的距离,两值相加小于x时,说明当前点也被感染。当最远未被感染的点距离为x时,说明此时必须覆盖此点已便感染儿子节点。
#include<iostream>
#include<vector>
using namespace std;
#define ll long long
const ll N=2e5+7;
const ll inf=1e9;
ll n,m;
vector<ll>ho[N];
ll f[N],d[N],X,cnt;
void dfs(ll p,ll fa){
f[p]=inf;
d[p]=0;
for(int i=0;i<ho[p].size();i++){
ll to=ho[p][i];
if(to==fa)continue;
dfs(to,p);
f[p]=min(f[p],f[to]+1);
d[p]=max(d[p],d[to]+1);
}
if(f[p]+d[p]<=X){
d[p]=-inf;
}
else if(d[p]==X){
cnt++;
f[p]=0;
d[p]=-inf;
}
}
bool check(ll x){
X=x;
cnt=0;
dfs(1,0);
if(d[1]>=0){
cnt++;
}
return cnt<=m;
}
int main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<n;i++){
ll u,v;
scanf("%lld%lld",&u,&v);
ho[u].push_back(v);
ho[v].push_back(u);
}
ll l=0,r=n,ans=n;
while(l<=r){
ll mid=(l+r)/2;
if(check(mid)){
ans=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
cout<<ans<<"
";
}