NOIP模拟75
前言
先吐槽一下出题人,T2 牛马数据连棵树都不是。。
T3 描述不清楚。。
T1 如何优雅的送分
解题思路
我考场上还真以为是个送分题,然而。。。
莫比乌斯反演。。。
对于一个数字 n 有 \(2^{F(n)}=\sum\limits_{d|n}\mu^2(d)\) ,由于有平方因子的数字的 \(\mu\) 的值是 0 ,就相当于在所有的质因数里面选择若干个质因数组合。
\(\mu^2(n)=\sum\limits_{d^2|n}\mu(d)\) 对于质数的情况是比较显然的,毕竟符合条件的只有 1 。
这个可以理解为枚举质因数的个数算对应的答案。
假设这个数质因数的个数是 \(cnt\) ,那么答案就是 \(\sum\limits_{i=0}^{cnt}(-1)^i\times \binom{cnt}{i}\) 由二项式定理可得上式为 0 。
于是答案就是 \(\displaystyle\sum_{i=1}^n 2^{F(i)}=\sum_{i=1}^n\sum_{d|i}\sum_{k^2|d}\mu(k)\)
更改枚举顺序 \(\displaystyle\sum_{k=1}^n\mu(k)\sum_{k^2|d}\lfloor\frac{n}{d}\rfloor\)
也就是 \(\displaystyle\sum_{k=1}^n\mu(k)\sum_{i=1}^{\lfloor\frac{n}{k^2}\rfloor}\lfloor\frac{n}{k^2i}\rfloor\)
设 \(S(n)=\sum\limits_{i=1}^n\lfloor \frac{n}{i}\rfloor\) 上述柿子就是 \(\sum\limits_{k=1}^n \mu(k)S(\lfloor\frac{n}{k^2}\rfloor)\) 。
对于 \(S\) 的运算可以数论分块 复杂度是 \(\mathcal{O}(\sqrt{n}logn)\)
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e6+7,mod=1e9+7;
int n,m,ans,cnt,pri[N],mu[N];
bool vis[N];
void Pre_work()
{
mu[1]=1;
for(int i=2;i<=m;i++)
{
if(!vis[i]) pri[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&pri[j]*i<=m;j++)
{
int temp=i*pri[j]; vis[temp]=true;
if(i%pri[j]==0){mu[temp]=0;break;}
mu[temp]=-mu[i];
}
}
}
int S(int lim)
{
int sum=0;
for(int l=1,r;l<=lim;l=r+1)
{
r=lim/(lim/l);
sum=(sum+lim/l%mod*(r-l+1)%mod)%mod;
}
return sum;
}
signed main()
{
freopen("elegant.in","r",stdin); freopen("elegant.out","w",stdout);
n=read(); m=sqrt(n)+1; Pre_work();
for(int i=1;i<=n;i++)
{
int temp=S(n/(i*i));
if(!temp) break;
ans=(ans+mu[i]*temp%mod+mod)%mod;
}
printf("%lld",ans);
return 0;
}
T2 阴阳
解题思路
正解不太会,打了个暴力,可能是由于数据水,骗到了 100pts (虽然后来被Hack成了 91pts)
比较好奇的就是出题人的数据怎么造的(原来 n 个点 n-1 条边还可以是有环的森林。。。)
暴力思路比较简洁,我们需要一个可以动态开空间并且支持查找并且删掉某一个值的数据结构,unoedered_set
刚好可以满足我们的需求。
于是我们可以维护每一个版本的所有黑色节点,每次暴扫整个 set
求出答案。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10,M=3e3+10,INF=1e9;
int typ,n,m,t,ans,cnt,now,s[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
int tim,dfn[N],siz[N],fa[N],son[N],dep[N],topp[N];
unordered_set<int> res[N];
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(siz[to]) continue;
fa[to]=x; dep[to]=dep[x]+1;
dfs1(to); siz[x]+=siz[to];
if(siz[to]>siz[son[x]]) son[x]=to;
}
}
void dfs2(int x,int tp)
{
topp[x]=tp; dfn[x]=++tim;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=nxt[i])
if(!dfn[ver[i]])
dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]]) swap(x,y);
x=fa[topp[x]];
}
if(dep[x]>dep[y]) swap(x,y);
return x;
}
int dist(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
signed main()
{
freopen("yygq.in","r",stdin); freopen("yygq.out","w",stdout);
typ=read(); n=read();
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
dfs1(1); dfs2(1,1); m=read();
while(m--)
{
int opt,x; opt=read(); x=read()^(typ*ans);
if(opt==1)
{
cnt++; res[cnt]=res[now];
if(res[now].find(x)==res[now].end()) res[cnt].insert(x);
else res[cnt].erase(x); now=cnt; continue;
}
if(opt==2)
{
ans=INF;
for(auto it:res[now]) ans=min(ans,dist(x,it));
printf("%lld\n",ans); continue;
}
now=x;
}
return 0;
}
T3 你猜是不是找规律
解题思路
我还真就以为是个找规律了。。。
出题人描述不清楚,题目认为的有序序列只是一种顺序只能是升序或者只能是降序。
DP 转移 \(f_{i,j}\) 表示对于有序的序列前 i 个数字交换 j 次可以得到的不同序列。
就有方程:\(f_{i,j}=f_{i-1,j}+f_{i-1,j-1}\times(i-1)\) 。
然后发现其实前缀和是一个有 \(2k\) 项的多项式(我也不知道为啥是这个。。),然后直接拉格朗日插值。。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=3e3+10,mod=1e9+7;
int n,m,ans,f[2][N],s[N<<1];
int power(int x,int y,int p=mod)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%p;
x=x*x%p; y>>=1;
}
return temp;
}
void solve1()
{
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++) f[i&1][j]=f[(i&1)^1][j];
for(int j=1;j<=min(i,m);j++) f[i&1][j]=(f[i&1][j]+(i-1)*f[(i&1)^1][j-1])%mod;
}
for(int i=0;i<=m;i++) ans=(ans+f[n&1][i])%mod;
printf("%lld",ans); exit(0);
}
signed main()
{
freopen("guess.in","r",stdin); freopen("guess.out","w",stdout);
n=read(); m=read(); f[0][0]=1;
if(n<=3000) solve1();
for(int i=1;i<=2*m+1;i++)
{
for(int j=0;j<=m;j++) f[i&1][j]=f[(i&1)^1][j];
for(int j=1;j<=min(i,m);j++)
f[i&1][j]=(f[i&1][j]+(i-1)*f[(i&1)^1][j-1])%mod;
for(int j=0;j<=m;j++) s[i]=(s[i]+f[i&1][j])%mod;
}
for(int i=1;i<=2*m+1;i++)
{
int temp=1,base=1;
for(int j=1;j<=2*m+1;j++)
if(i!=j)
temp=(n-j+mod)%mod*temp%mod,
base=base*(i-j+mod)%mod;
ans=(ans+s[i]*temp%mod*power(base,mod-2))%mod;
}
printf("%lld",ans);
return 0;
}
T4 小说
解题思路
运用退背包每次枚举退掉的点,选择造成损失比较小的,方案可以在对于大质数取模的意义下考虑。
然后有了这个去掉的物品,接下来我们所选择的 b 不可以被剩下物品 \(v_i,-v_i\) 构成。
对于剩下的物品按上面两个值做一个 01背包,取大于 0 的第一个值作为答案。
code
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=110,M=7e5+10,mod=1e9+7,base=7e5+10;
int n,lim,ans,maxn,id,s[N],f[M],g[M];
bitset<(M<<1)> bit;
signed main()
{
freopen("novel.in","r",stdin); freopen("novel.out","w",stdout);
n=read(); f[0]=1; for(int i=1;i<=n;i++) s[i]=read(),lim+=s[i];
for(int i=1;i<=n;i++)
for(int j=lim;j>=s[i];j--)
f[j]=(f[j]+f[j-s[i]])%mod;
for(int i=1;i<=n;i++)
{
int sum=0;
for(int j=0;j<=lim;j++) g[j]=f[j];
for(int j=s[i];j<=lim;j++) g[j]=(g[j]-g[j-s[i]]+mod)%mod;
for(int j=1;j<=lim;j++) sum+=(g[j]>0);
if(sum>maxn) maxn=sum,id=i;
}
bit[base]=true;
for(int i=1;i<=n;i++) if(i!=id) bit|=bit>>s[i],bit|=bit<<s[i];
for(int i=1;i<=lim+1;i++) if(!bit[i+base]){ans=i;break;}
printf("%lld %lld",s[id],ans);
return 0;
}