NOIP模拟13「工业题·卡常题·玄学题」
T1:工业题
基本思路
这题有一个重要的小转化:
我们将原来的函数看作一个矩阵,\(f(i,j-1)*a\)相当于从\(j-1\)向右走一步并贡献a,\(f(i-1,j)*b\)相当于从\(i-1\)向下走一步并贡献b
那么问题就转化成了求从第\(0\)行与第\(0\)列的所有点走到点\((m,n)\)的所有方案数的总贡献
在一个点,对于他之前的点的所有走法,他都有可能向下或右走并带来贡献,所以是统计所有方案数。
易知从点\((i,j)\)到点\((m,n)\)的走的步数是\(m-i+n-j)\),又由于在每一个点都有向下或向右走的可能,我们可以只关注向下(或向右),看每一步向下(或向右)走发生在哪个点,那么方案数就是\(C_{n+m-i}^{n-i}\)(从第0列出发)及\(C_{n+m-i}^{m-i}\)(从第0行出发)
你这么打了,然后你WA了。。。。。。
为什么?
注意:这里的C考虑的是决策点,但是,第0行(列)的点一定会向下(右)走,是不会有决策的,因而正确的式子是:
\]
上代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
const int SIZE=3e5+4;
const int mod=998244353;
int n,m;
ll a,b,ans;
ll jc[SIZE<<1],inv[SIZE<<1];
ll fn[SIZE],fm[SIZE];
ll b_,a_;
ll qpow(ll base,ll exp)
{
rr ll ret=1ll;
while(exp)
{
if(exp&1) ret=ret*base%mod;
base=base*base%mod;
exp>>=1ll;
}
return ret;
}
inline ll C(ll x,ll y){return jc[x]*inv[y]%mod*inv[x-y]%mod;}
ll read1()
{
rr ll x_read=0ll,y_read=1ll;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=x_read*10+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
int read2()
{
rr int x_read=0ll,y_read=1ll;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=x_read*10+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace STD;
int main()
{
n=read2(),m=read2(),a=read1(),b=read1();
a%=mod,b%=mod;
a_=qpow(a,m);
b_=qpow(b,n);
//由于后面a^m与b^n会大量用到,所以预处理
for(rr int i=1;i<=n;i++){fn[i]=read1();fn[i]%=mod;}
for(rr int i=1;i<=m;i++){fm[i]=read1();fm[i]%=mod;}
jc[0]=inv[0]=1ll;
for(rr int i=1;i<=n+m;i++){jc[i]=1ll*i*jc[i-1]%mod;inv[i]=qpow(jc[i],mod-2);}
for(rr int i=1;i<=n;i++)
ans=(ans+fn[i]*C(n+m-i-1,m-1)%mod*qpow(b,n-i)%mod*a_%mod)%mod;
for(rr int i=1;i<=m;i++)
ans=(ans+fm[i]*C(n+m-i-1,n-1)%mod*b_%mod*qpow(a,m-i)%mod)%mod;
int x=printf("%lld",ans);
}
T2:
基本思路:
考场上想的普通DP,不对。
实际上是树形DP
实际上可以将Y方点看作边,然后将X方点看作点,\(n\)个点\(n\)条边,很明显,去掉一条边后就是树,我们的任务就是求能将所有的边都覆盖的最小代价,一条边被覆盖是指它的两个端点至少有一个被选。
我们可以判出环来然后删去环里的任一条边,然后以删掉的边的端点跑DP即可
很明显树形DP,DP式就不写在这了,自己推吧,推不出来就自己上代码里找吧。
状态数组\(c[i][j](j=0,1)\)表示i被选(j=1)或不被选(j=0)是以他为根的子树的最小代价。
注意被删掉的边也要被覆盖,所以要在两个端点被选的情况里取min
代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
#define pp pair<int,int>
#define fi first
#define se second
#define min(x,y) (x<y?x:y)
#define inf LONG_LONG_MAX
#define IT set<int>::iterator
const int SIZE=1e6+4;
int n,root;
ll ans=inf,a,b;
set<int> to[SIZE];
ll val[SIZE];
ll c[SIZE][2];
bool vis[SIZE];
pp edge;
void dfs(int x,int f)
{
static bool ret=0;
vis[x]=1;
for(IT it=to[x].begin();it!=to[x].end();it++)
{
if(*it==f) continue;
if(vis[*it]){edge.fi=x,edge.se=*it;ret=1;return;}
dfs(*it,x);
if(ret) return;
}
}
void DP(int now,int f)
{
//儿子与父亲必须至少有一个选
for(IT it=to[now].begin();it!=to[now].end();it++)
{
if(*it==f) continue;
DP(*it,now);
c[now][1]+=min(c[*it][0],c[*it][1]);
c[now][0]+=c[*it][1];
//鄙视你的智商,连我都推出来的式子你还推不出来过来看。。。。
}
c[now][1]+=val[now];
}
int read()
{
rr int x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read=-1;
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read*10)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace STD;
int main()
{
n=read(),a=read(),b=read();
for(rr int i=1;i<=n;i++)
{
int x1=read(),x2=read();
to[x1].insert(x2),to[x2].insert(x1);
val[x1]+=a,val[x2]+=b;
}
dfs(1,1);
to[edge.fi].erase(edge.se);
to[edge.se].erase(edge.fi);
root=edge.fi;
DP(root,root);
ans=min(c[root][1],ans);
for(rr int i=1;i<=n;i++) c[i][0]=c[i][1]=0;
root=edge.se;
DP(root,root);
ans=min(ans,c[root][1]);
printf("%lld",ans);
}
T3:玄学题
基本思路
当我看到-1的时候就已经想到要判指数的奇偶了,但是不会高效地判。
其实想来也很简单(事后诸葛亮),\(d\)为偶数的情况不决定奇偶,只有奇数的情况才决定,而约数数为奇数只有完全平方数一种情况。
证明:
对于每个数,他本身以及1都是他的约数,这就有两个约数了。
假如一个数能被某个数整除,这个数及得数都是其约数,那么,除了某个数平方是这个数,那么他的约数一定成对存在。
而假如有个数的平方是这个数,那么它的商还是他自己,那么他这一“对”就只有他自己。
这个平方数约数个数为奇数。
假如有\(i=p*x^2\)
要想使\(d(i*j)\)是奇数那么j必须满足\(j=p*y^2\),那么我们只要统计\([1,m]\)内有几个j满足条件即可。
首先我们可以写一个线性筛法求出每个i的p,并记录。
线性筛法:
p[1]=1;
for(rr int i=2;i<=en;i++)
p[i*i]=1;
for(rr int i=2;i<=n;i++)
if(!p[i])
{
p[i]=i;
for(rr int j=2;j<=en;j++)
{
if(j*j*i>n) break;
p[j*j*i]=i;
}
}
然后,就有j的个数
\]
\]
证明:
平方在[1,m]范围内的数最多有\(\sqrt{m}\)个那么平方后\(p\)倍还在范围内的就有\(\sqrt{\frac{m}{p}}\)个
那么我们的算法就是\(O(n)\)的了。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace STD
{
#define ll long long
#define rr register
const int SIZE=1e7+4;
int n,ans;
ll m;
int p[SIZE];
ll read()
{
rr ll x_read=0,y_read=1;
rr char c_read=getchar();
while(c_read<'0'||c_read>'9')
{
if(c_read=='-') y_read='-';
c_read=getchar();
}
while(c_read<='9'&&c_read>='0')
{
x_read=(x_read*10)+(c_read^48);
c_read=getchar();
}
return x_read*y_read;
}
};
using namespace STD;
int main()
{
n=read(),m=read();
int en=sqrt(n);
p[1]=1;
for(rr int i=2;i<=en;i++)
p[i*i]=1;
for(rr int i=2;i<=n;i++)
if(!p[i])
{
p[i]=i;
for(rr int j=2;j<=en;j++)
{
if(j*j*i>n) break;
p[j*j*i]=i;
}
}
for(rr int i=1;i<=n;i++)
{
ll x=sqrt(m/(ll)p[i]);
if(x&1ll) ans--;
else ans++;
}
printf("%d",ans);
}
最新文章
- Java线程:线程的同步-同步方法
- Objective-C Runtime 运行时之三:方法与消息
- 【HDOJ】3183 A Magic Lamp
- 模拟jquery封装选择器
- iOS开发之基于parse的登录注册
- windows后台服务程序编写
- webpack基础打包安装分享
- [OI]Noip 2018总结(普及)
- HTML基础之HTML标签-html header(meta,title) html body(p,br,h,form,div,span,input,lable)
- enumerate使用
- 浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序
- java的基本数据类型默认值
- python 抓取alexa数据
- ios 让两个tableView同时处于选中状态
- Centos7服务器搭建VNC Server环境
- 514. Freedom Trail
- 批量处理任务进度条控制—基于BackgroundWorker
- spark DiskBlockManager
- Celery-4.1 用户指南: Application(应用)
- 机器学习--近邻成分分析(NCA)算法 和 度量学习