noip模拟13
这场考试整体状态还算不错,基本上没有挂分,不会的是真不会了……
衡中巨佬有上200的
A. 工业题
其实这是组合题……
首先考虑从一个边上的点走到右下角,每往右走一次乘一个 a,每往下走一次乘一个 b,还需要乘上一个系数,这个系数就是走到右下角的方案数
这是一个经典的组合问题,答案为 \(\displaystyle\binom{x+y}{x}\)
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int maxn=1e6+5;
int n,m,a,b,ans,fac[maxn],inv[maxn],x[maxn],y[maxn];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
int po(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
int c(int n,int m){
if(n==m)return 1;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
n=read();
m=read();
a=read()%mod;
b=read()%mod;
// swap(a,b);
for(int i=1;i<=n;i++){
x[i]=read()%mod;
}
for(int i=1;i<=m;i++){
y[i]=read()%mod;
}
fac[0]=1;
inv[0]=1;
for(int i=1;i<=n+m;i++){
fac[i]=fac[i-1]*i%mod;
inv[i]=po(fac[i]);
}
for(int i=1;i<=n;i++){
ans=(ans+x[i]*po(a,m)%mod*po(b,n-i)%mod*c(m+n-i-1,m-1)%mod)%mod;
}
for(int i=1;i<=m;i++){
ans=(ans+y[i]*po(a,m-i)%mod*po(b,n)%mod*c(m+n-i-1,n-1)%mod)%mod;
}
cout<<ans<<endl;
return 0;
}
B. 卡常题
其实这是图论题……
一眼望过去这种输入格式莫名有一种现成连边的亲切感,但是直接建出边后的图不知道是个啥(说实话其实是没看见保证图联通),想了半天网络流的东西
正解是 \(x\) 点相当于是带权点,\(y\) 点相当于需要覆盖的无权边,而 \(n\) 个点 \(n\) 条边的连通图是基环树
于是变成基环树上的边覆盖问题,随便拆掉换上的一条边进行 \(dp\) 即可
关于 \(dp\),一种很方便的做法是以 \(x\) 为根来一遍,以 \(y\) 为根来一遍,之后取个最小值即可
代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int maxm=2e6+5;
int n,m,f[maxn][2],hd[maxn],cnt,ans,pos,val[maxn],a,b,x,y;
bool vis[maxn];
struct Edge{
int nxt,to;
}edge[maxm];
void add(int u,int v){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
hd[u]=cnt;
return ;
}
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
void dfs(int u,int last){
vis[u]=true;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(i==(last^1))continue;
if(vis[v]){
pos=i;
return ;
}
else dfs(v,i);
}
return ;
}
void dfs1(int u,int father){
f[u][1]=val[u];
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==father||i==pos||i==(pos^1))continue;
dfs1(v,u);
f[u][0]+=f[v][1];
f[u][1]+=min(f[v][0],f[v][1]);
}
return ;
}
int main(){
n=read();
a=read();
b=read();
cnt=1;
for(int i=1;i<=n;i++){
x=read();
y=read();
val[x]+=a;
val[y]+=b;
add(x,y);
add(y,x);
}
dfs(1,0);
x=edge[pos].to;
y=edge[pos^1].to;
dfs1(x,0);
ans=f[x][1];
memset(f,0,sizeof f);
dfs1(y,0);
cout<<min(ans,f[y][1]);
return 0;
}
C. 玄学题
其实这是数学题……
首先一个来想 \(nm\) 的做法,有60分部分分的好成绩
一个直接的想法是能 \(O(1)\) 算出约数个数,这个可以线性筛(但是考场上忘了)
再来重推一遍柿子
对于一个质数 \(p\),约数个数为2;
如果 \(p\nmid i\),显然 \(d[p*i]=d[i]*d[p]=d[i]*2\)
否则,把柿子展开:
可以看出
好的,现在来看正解:
因为这个奇怪的问法,发现 \(-1\) 的次方只与奇偶有关
然后可以发现因数个数为偶数的数为完全平方数,那么问题变成 \(ij\) 有几个是完全平方数
设 \(i=q*x^2\),那么j必须是 \(q*y^2\) 的形式
如果求出了q,个数即为 \(\displaystyle\sqrt{\frac{m}{q}}\)
那么q直接暴力筛即可
代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+5;
#define int long long
int n,m,p[maxn],ans;
bool flag[maxn];
signed main(){
cin>>n>>m;
for(int i=1;i*i<=n;i++){
for(int j=1;j*i*i<=n;j++){
p[i*i*j]=j;
}
flag[i*i]=true;
}
int s=sqrt(m);
for(int i=1;i<=n;i++){
if(flag[i]){
if(s&1)ans--;
else ans++;
}
else{
int x=sqrt(m/p[i]);
if(x&1)ans--;
else ans++;
}
}
cout<<ans;
return 0;
}