做题笔记(三)
[USACO13DEC] Vacation Planning G
题目传送门
[USACO13DEC] Vacation Planning G
思路
总结:一点点小思维+最短路板子。
显然我们发现,对于每一次询问的出发点 \(u\) 只有两种可能:是中枢或者不是中枢。
同时注意到,\(K\) 的范围非常小,所以可以考虑在中枢上做文章。我们可以先试着在 \(K\) 个中枢中跑 \(K\) 遍最短路。然后分类讨论(假定中枢点集合为 \(S\)):
-
\(u\in S\):显然我们已经处理过了 \(u\) 的最短路,所以我们可以直接判断 \(u\) 是否可以到 \(v\) 并且很快查询其花费。
-
\(u\notin S\):根据题目性质可以得出每一个与 \(u\) 相连的节点 \(w\) 都满足 \(w\in S\),而每一个 \(w\) 的最短路也已知,所以可以遍历一遍 \(u\) 所能到达的点再查询花费。
注意到每一个中枢的范围在 \([1,N]\),似乎二维的最短路数组存不下,那就先给它离散化一下吧,这样每一个中枢的范围就控制在了 \([1,K]\)。
似乎 SPFA 都可以跑过去,但我们还是选择 Dijkstra。
最坏时间复杂度为 \(O(KN\log(N+M)+KQ)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-') f=-1;
for(;c>='0'&&c<='9';c=getchar())
x=x*10+c-'0';
return x*f;
}
inline void Write(int x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9) Write(x/10);
putchar(x%10+'0');
}
inline void write(int x){
Write(x);
putchar('\n');
}
const int MAXN=20005,MAXK=205;
int n,m,k,q;
vector<pair<int,int> >V[MAXN];
int hub[MAXK],id[MAXN]={0},cnt=0;
int dis[MAXK][MAXN],tot=0,ans=0;
bool vis[MAXN]={0},ishub[MAXN]={0};
void dijkstra(int s){
for(int i=1;i<=n;i++)
vis[i]=0;
priority_queue<pair<int,int> >q;
dis[id[s]][s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
int t=q.top().second;
q.pop();
if(vis[t]) continue;
else vis[t]=1;
for(int i=0;i<V[t].size();i++){
int to=V[t][i].first;
int w=V[t][i].second;
if(dis[id[s]][to]>dis[id[s]][t]+w){
dis[id[s]][to]=dis[id[s]][t]+w;
q.push(make_pair(-dis[id[s]][to],to));
}
}
}
}
signed main(){
n=read(),m=read();
k=read(),q=read();
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
dis[i][j]=1e17;
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
V[u].push_back(make_pair(v,w));
}
for(int i=1;i<=k;i++){
hub[i]=read();
ishub[hub[i]]=1;
id[hub[i]]=(++cnt);
}
for(int i=1;i<=k;i++)
dijkstra(hub[i]);
while(q--){
int u=read(),v=read();
if(ishub[u]){
if(dis[id[u]][v]!=1e17){
tot++;
ans+=dis[id[u]][v];
}
}
else{
int mn=1e17;
for(int i=0;i<V[u].size();i++){
int to=V[u][i].first;
int w=V[u][i].second;
mn=min(dis[id[to]][v]+w,mn);
}
if(mn!=1e17){
tot++;
ans+=mn;
}
}
}
write(tot);
write(ans);
return 0;
}
【模板】卢卡斯定理/Lucas 定理
题目传送门
思路
今天学的一个小定理:
主要用于求解大组合数取模大质数问题。
附:
-
威尔逊定理:\((p-1)!\equiv-1(\mod p)\)。
-
费马小定理:\(a^{p-1}\equiv1(\mod p)\)。
-
裴蜀定理:\(\sum_ix_ia_i=d\) 成立当且仅当 \(\gcd(a_{1\rightarrow n})\mid d\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXP=1e5+5;
int n,m,p=1e9+7,lc[MAXP]={1};
int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=((res%p)*(a%p))%p;
a=((a%p)*(a%p))%p;
b>>=1;
}
return res;
}
int getC(int n,int m){
if(m>n) return 0;
int a=lc[n];
int b=qpow(lc[m],p-2);
int c=qpow(lc[n-m],p-2);
return (a*b%p*c%p);
}
int lucas(int n,int m){
if(m==0) return 1;
int a=getC(n%p,m%p);
int b=lucas(n/p,m/p);
return a*b%p;
}
void solve(){
scanf("%lld%lld%lld",&n,&m,&p);
for(int i=1;i<=p;i++)
lc[i]=(lc[i-1]*i)%p;
printf("%lld\n",lucas(n+m,n));
}
signed main(){
int t; scanf("%lld",&t);
while(t--) solve();
return 0;
}
[GESP202312 八级] 奖品分配
题目传送门
思路
组合数学基础题。
首先,面对组合数学,我们需要学会转化问题。首先,我们可以把这个问题转化为:有 \(N\) 个盒子,\(M\) 种物品,每种物品有 \(a_i\) 个,填入这 \(N\) 个盒子中的方案数。
这类填坑问题一般以物品为切入点。考虑第 \(i\) 个物品,假设剩余了 \(K\) 个坑,那么其贡献的方案因子为 \(\dbinom{K}{a_i}\),填完之后剩余的坑的数量就是 \(K-a_i\) 个。
然后根据乘法原理即可,时间复杂度 \(O(T(N+M))\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int n,m,a[1005],C[2005][2005]={0};//n,m
void getC(){
for(int i=0;i<=2000;i++)
C[i][0]=1;
for(int i=1;i<=2000;i++){
for(int j=1;j<=i;j++){
C[i][j]=C[i-1][j]+C[i-1][j-1];
C[i][j]%=MOD;
}
}
}
void solve(){
int tot=0,res=1;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
scanf("%lld",&a[i]);
tot+=a[i];
}
if(tot!=n) n++;
for(int i=1;i<=m;i++){
if(a[i]!=0){
res*=C[n][a[i]];
res%=MOD;
n-=a[i];
}
}
printf("%lld\n",res);
}
signed main(){
getC();
int t; scanf("%lld",&t);
while(t--) solve();
return 0;
}
[GESP202403 八级] 公倍数问题
题目传送门
思路
假设一个数 \(a\) 的因子个数是 \(b\),那么显然 \(a\) 是 \(b^2\) 个有序数对的公倍数。所以这道题的难度在于预处理 \(1\rightarrow K\) 的不大于 \(N\) 和 \(M\) 的因子个数。
显然有一种方法:对于一个数 \(i\),那么它一定是 \(i,i+i,i+i+i\cdots\) 的因子。
那么这个方法的整体复杂度就是 \(O(\sum^N\lfloor\frac{K}{i}\rfloor+\sum^M\lfloor\frac{K}{i}\rfloor+K)\)。
我们分析一下复杂度:显然前面的两个 sigma 代表的是两个调和级数求和。直接分析的话还是非常有难度的,因为它是发散的,所以我们可以用一个程序来计算其语句进行次数。事实证明,在 \(N\) 与 \(K\) 等价并且 \(N\approx10^{k},4\le k\le6\) 时,其运行次数总是小于 \(N\) 的 \(\frac{3}{2}\),即:\(\sum^N\lfloor\frac{N}{i}\rfloor<1.5N\),所以可以通过 1ms 的时间限制。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXK=1e6+5;
int n,m,k,inn[MAXK]={0},inm[MAXK]={0},tot=0;
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=i;j<=k;j+=i)
inn[j]++;
}
for(int i=1;i<=m;i++){
for(int j=i;j<=k;j+=i)
inm[j]++;
}
for(int i=1;i<=k;i++)
tot+=(i*inn[i]*inm[i]);
printf("%lld\n",tot);
return 0;
}

浙公网安备 33010602011771号