可达 2025 国庆集训笔记
Day 1
数论
扩展欧几里得算法
求 \(\gcd(a,b)\) 同时求出关于 \(x,y\) 的方程 \(ax+by=\gcd(a,b)\) 的一组整数解。
首先,当 \(b=0\) 时,原方程变为 \(ax=|a|\),解得 \(x=\pm1\) 且 \(y\) 为任意整数。
设 \(\gcd(a,b)=\gcd(b,a\bmod b)=bx'+(a\bmod b)y'\),又有 \(a\bmod b=a-\left\lfloor\dfrac{a}{b}\right\rfloor\cdot b\),所以
又因为 \(ax+by=\gcd(a,b)\),所以 \(x=y',y=(x'-\left\lfloor\dfrac{a}{b}\right\rfloor\cdot y')\)。具体实现时递归求解即可。
代码:
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll xx,yy;
ll g=exgcd(b,a%b,xx,yy);
x=yy;
y=xx-a/b*yy;
return g;
}
乘法逆元
如果存在 \(ax\equiv1\pmod m\),则称 \(x\) 为 \(a\) 模 \(m\) 下的乘法逆元。\(a\) 模 \(m\) 下有乘法逆元的充要条件为 \(\gcd(a,m)=1\)。
模数 \(m\) 为质数时可用费马小定理求逆元。费马小定理:若 \(p\) 是质数且 \(\gcd(a,p)=1\),则 \(a^{p-1}\equiv1\pmod p\)。将这个式子变形一下,得 \(a\times a^{p-2}\equiv1\pmod p\),由此可得 \(a^{p-2}\) 即为 \(a\) 模 \(p\) 下的乘法逆元。具体实现可用快速幂,代码不放了。
欧拉函数与欧拉定理
欧拉函数:\(\phi(n)\),表示小于等于 \(n\) 的正整数中与 \(n\) 互质的数的个数。
性质:
- 如果 \(\gcd(x,y)=1\),则 \(\phi(x\times y)=\phi(x)\times\phi(y)\)。
- 若 \(p\) 是质数,\(k\ge1\),则 \(\phi(p^k)=p^k-p{k-1}=p^{k-1}(p-1)\)。
通项公式:\(\phi(n)=n\times\prod(1-\dfrac{1}{p_i})\),其中 \(n=\prod p_i^{k_i}\),\(p_i\) 为互不相同的质数。证明:
欧拉定理:若 \(\gcd(a,n)=1\),则 \(a_{\phi(n)}\equiv1\pmod n\)。
扩展欧拉定理:\(a^b\equiv a^{b\bmod\phi(n)+\phi(n)}\pmod n\)。
Day 1 模拟赛
A
题意
给定一个正整数 \(n\),可以进行若干次操作,每次操作可选择一个两两不同整数 \(z=p^k\),其中 \(p\) 为质数,\(k\) 为正整数,且 \(z|n\),操作后 \(n\to\dfrac{n}{z}\)。求最多操作次数。
题解
先分解质因数。然后显然可以对于每个质因数操作,设 \(k\) 为质因数的次数,操作次数就是满足 \(\sum_{i=1}^{x}i\le k\) 的最大正整数 \(x\),这个值可以直接暴力算。
代码:
#define ll long long
const int _mxn=+5;
ll n;
int main()
{
___();
cin>>n;
int ans=0;
for(ll i=2;i*i<=n;i++)//i 要定义成 long long,不然取模时类型转换会 T,场上因此挂成了 76
{
int cnt=0;
while(n%i==0)//求当前质因数次数
{
n/=i;
cnt++;
}
int x=1;
while(cnt>=x)//求出 x
{
ans++;
cnt-=x;
x++;
}
}
if(n>1)//可能没分完剩一个
ans++;
cout<<ans<<endl;
return 0;
}
B
题意
给定整数 \(a,b,c\),有一个坐标 \((x,y)\) 满足 \(ax+by+c=0\) 且 \(x,y\) 为正整数,\(-5\times10^{18}\le x,y\le5\times10^{18}\),求出任意一个满足条件的坐标,没有输出 \(-1\)。
题解
exgcd 板子。方程变形一下得 \(ax+by=-c\),所以只要 \(\gcd(a,b)|c\) 就有解,否则无解。exgcd 求一下然后 \(\times\dfrac{-c}{\gcd(a,b)}\) 即可。这个范围非常大,不用判断。
代码:
#define ll long long
const int _mxn=+5;
ll a,b,c;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll xx,yy;
ll g=exgcd(b,a%b,xx,yy);
x=yy;
y=xx-a/b*yy;
return g;
}
int main()
{
___();
cin>>a>>b>>c;
ll x,y;
ll g=exgcd(a,b,x,y);
if(c%g==0)
{
cout<<-c/g*x<<" "<<-c/g*y<<endl;
}
else
cout<<-1<<endl;
return 0;
}
C
题意
给定 \(a,b,n,l,r\),求是否存在整数 \(k\) 满足 \(l\le k\le r\) 且存在非负整数 \(m\) 使 \(n−m\times b=k\times a\)。
题解
把式子变形一下,得 \(ak+bm=n\),显然就是 exgcd,\(\gcd(a,b)|n\) 时有解。设 \(g=\gcd(a,b)\),把 \(a,b,n\) 全都 \(\div g\),然后 exgcd 求一下 \(k,m\)。显然,如果 \(ak+bm=n\),那么 \(a(k+cb)+b(m-ca)=n\),那么我们先把 \(k\) 缩小,\(\bmod b\) 即可,然后找到 \(\le r\) 最大的 \(k+cb\),看看是否 \(\ge l\) 就是答案了。
代码:
#define ll long long
const int _mxn=+5;
ll a,b,n,l,r;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll xx,yy;
ll g=exgcd(b,a%b,xx,yy);
x=yy;
y=xx-a/b*yy;
return g;
}
int main()
{
___();
int _;
cin>>_;
while(_--)
{
cin>>a>>b>>n>>l>>r;
//k*a+m*b=n
ll k,m;
ll g=__gcd(a,b);
if(n%g==0)
{
a/=g,b/=g,n/=g;
exgcd(a,b,k,m);
k=(k*n%b+b)%b;
ll x=(r-k)/b;
//(k+xb)a+(m-xa)b=n
if(l<=k+x*b&&k+x*b<=r)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
else
cout<<"NO"<<endl;
}
return 0;
}
D
题意
题解
利用扩展欧拉定理降幂,可得 \(a_n\bmod p=2^{a_{n-1}\bmod\phi(p)+\phi(p)}\bmod p\),那么就可以递归求解。先打一个暴力找到每个 \(p\) 的 \(a_{1...100}\),发现所有都在 \(a_{100}\) 左右就稳定了,所以我们可以直接输出一个下标较大(如 \(114\))的 \(a_i\)。
代码:
#define ll long long
const int _mxp=1e7+5;
int p;
int phi[_mxp];
void eular(int n)//预处理欧拉函数
{
for(int i=1;i<=n;i++)
phi[i]=i;
for(int i=2;i<=n;i++)
{
if(phi[i]==i)
{
for(int j=i;j<=n;j+=i)
phi[j]=phi[j]/i*(i-1);
}
}
}
ll qpow(ll x,ll n,int m)//快速幂
{
ll res=1,bs=x;
while(n>0)
{
if(n&1)
res=res*bs%m;
n>>=1;
bs=bs*bs%m;
}
return res;
}
ll solve(ll n,int p)//求 a[n]%p
{
if(n==0)
return 1%p;
return qpow(2,solve(n-1,phi[p])+phi[p],p);
}
int main()
{
___();
eular(1e7);
int _;
cin>>_;
while(_--)
{
cin>>p;
cout<<solve(114,p)<<endl;
}
return 0;
}
Day 2
搜索
Day 2 模拟赛
A
题意
给定一个长 \(n\) 的数列 \(a\),把这个数列复制 \(n\) 次首尾拼接得到一个新数列,求新数列的最长上升子序列长度。
题解
复制 \(n\) 次后,可以发现原数列中每个数都可以在任意一段原数列中被选,所以最长上升子序列长度即为原序列的数字种数,这里使用排序+去重,也可以直接用 set。
代码:
const int _mxn=1e5+5;
int n,a[_mxn];
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
int t=unique(a+1,a+n+1)-a-1;
cout<<t<<endl;
return 0;
}
B
题意
给定一个 \(n\times m\) 的由 -
和 #
组成的字符矩阵,求 #
区域的个数。当两个 #
的曼哈顿距离(\(|x1-x2|+|y1-y2|\)) \(\le2\) 时,这两个 #
在同一区域。
题解
直接爆搜,不过方向数组不太一样。如图,其中 *
处如果有 #
和图中的 #
在同一区域:
..*..
.***.
**#**
.***.
..*..
那么就共有 \(12\) 种方向,具体看代码:
#define ll long long
const int _mxn=100+5;
int n,m;
char a[_mxn][_mxn];
int dx[]={1,0,-1,0,1,1,-1,-1,2,0,-2,0},dy[]={0,1,0,-1,1,-1,1,-1,0,2,0,-2};
void dfs(int x,int y)
{
a[x][y]='-';//覆盖掉
for(int i=0;i<12;i++)
{
int tx=x+dx[i],ty=y+dy[i];
if(a[tx][ty]=='#')
dfs(tx,ty);
}
}
int main()
{
___();
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j]=='#')
{
ans++;//累加答案
dfs(i,j);//覆盖和它在同一区域的
}
}
}
cout<<ans<<endl;
return 0;
}
C
题意
有一张 \(R\times C\) 的地图,上面标注了可行走的石砖 .
、不可破坏的石壁 X
,一处初始的宝石位置 *
,宝石所在位置一定是可行走石砖。
地图有 \(N\) 个机关指令,指令包含一个方向,每次触发机关指令,宝石会沿该方向移动任意多格(至少 \(1\) 格),但只能停在可通行的地砖上,不能穿过屏障或超出地图。需要找出宝石最终所有可能停落的位置,并用 *
在地图上标记出来。
题解
直接搜+剪枝。只需要每走一步分两种:继续走或下一条指令,加个 vis
数组,搜到底就把当前位置改成 *
即可。注意开始的位置要设成 .
。
代码:
#define ll long long
const int _mxn=50+5,_mxr=1000+5;
int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
int n,m,r,d[_mxr];
char a[_mxn][_mxn];
bool vis[_mxn][_mxn][_mxr];
void dfs(int x,int y,int k)//k-th op
{
if(vis[x][y][k])
return;
vis[x][y][k]=true;
if(k>r)
{
a[x][y]='*';
return;
}
int tx=x+dx[d[k]],ty=y+dy[d[k]];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&a[tx][ty]!='X')
{
dfs(tx,ty,k);
dfs(tx,ty,k+1);
}
}
int main()
{
___();
cin>>n>>m;
int sx,sy;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
if(a[i][j]=='*')
sx=i,sy=j,a[i][j]='.';
}
cin>>r;
for(int i=1;i<=r;i++)
{
string op;
cin>>op;
if(op=="SOUTH")
d[i]=0;
else if(op=="EAST")
d[i]=1;
else if(op=="NORTH")
d[i]=2;
else
d[i]=3;
}
dfs(sx,sy,1);
for(int i=1;i<=n;i++,cout<<endl)
for(int j=1;j<=m;j++)
cout<<a[i][j];
return 0;
}
D
题意
题解
Day 3
DP
换根 DP
g[u]: 剖去 u 子树以 u 的 fa 为根的答案
Day 3 模拟赛
A
题意
有 \(k\) 种食物共 \(n\) 份,最多吃 \(m\) 份,第 \(i\) 份食物含蛋白质 \(a_i\) 单位,为第 \(b_i\) 种,第 \(i\) 种食物最多吃 \(s_i\) 份,求最多摄入的蛋白质量。
题解
贪心 (场上写了一坨 DP 还过了)。把每份食物按蛋白质含量降序排序,然后依次判断是否超量并选即可。
代码:
#define ll long long
const int _mxn=200+5,_mxk=100+5;
int n,m,k;
int s[_mxk];
struct food
{
int a,b;
bool operator<(food &x) const
{
return a>x.a;
}
}a[_mxn];
int main()
{
___();
cin>>n>>m>>k;
for(int i=1;i<=k;i++)
cin>>s[i];
for(int i=1;i<=n;i++)
cin>>a[i].a>>a[i].b;
sort(a+1,a+n+1);
int ans=0;
for(int i=1;i<=n;i++)
{
if(m==0)
break;
if(s[a[i].b])
ans+=a[i].a,s[a[i].b]--,m--;
}
cout<<ans<<endl;
return 0;
}
B
题意
给定一棵树,以任意节点作为根节点,从根节点往叶子节点走,等概率选择分支,分别求以每个节点为根节点走到叶子结点经过的边数的期望值对 \(998244353\) 取模的值。
题解
换根 DP。设 \(f_u\) 表示以 \(rt\)(可以为任意值)为根从 \(u\) 开始走的期望,对于每个点 \(u\),枚举儿子 \(v\),则有:\(f_u=\dfrac{f_v+1}{t}\)。
设 \(g_u\) 为剖去 \(u\) 子树以 \(u\) 的父亲为根的答案,则有:
其中 \(son_i\) 表示 \(i\) 节点的儿子数。
\(i\) 为根的答案为 \(\dfrac{f_i\times son_i+g_i+[i\ne rt]}{son_i+[i\ne rt]}\)。
代码:
#define ll long long
const int _mxn=2e5+5,_mod=998244353;
ll inv(ll x)
{
ll res=1,bs=x,n=_mod-2;
while(n>0)
{
if(n&1)
res=res*bs%_mod;
bs=bs*bs%_mod;
n>>=1;
}
return res;
}
int n;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
int son[_mxn];
ll f[_mxn];//从 u 到叶子的期望
ll ans=0;
void dfs1(int u,int fa)
{
f[u]=0;
son[u]=g[u].size()-(fa!=0);
ll p=inv(son[u]);
for(int v:g[u])
{
if(v==fa)
continue;
dfs1(v,u);
f[u]+=(f[v]+1)*p%_mod;
}
}
ll ff[_mxn];
void dfs(int u,int fa)
{
for(int v:g[u])
{
if(v==fa)
continue;
ll ex=f[u]*son[u]%_mod-1-f[v]+ff[u]+(u!=1);
ex=(ex%_mod+_mod)%_mod;
ff[v]=ex*inv(son[u]-(u==1))%_mod;
dfs(v,u);
}
}
int main()
{
___();
cin>>n;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
dfs1(1,0);
dfs(1,0);
for(int i=1;i<=n;i++)
{
ll resi=f[i]*son[i]+ff[i]+(i!=1);
resi%=_mod;
resi=resi*inv(son[i]+(i!=1))%_mod;
cout<<resi<<endl;
}
return 0;
}
C
题意
有一个被分为 \(n\times m\) 个地块的花圃,现在要在里面种植玫瑰花。每个地块可能不能种玫瑰花,且玫瑰花不能上下左右相邻种植。求种植方案数 \(\bmod10^8\) 的值。
题解
状压 DP。设 \(f_{i,x}\) 表示第 \(i\) 行状态为 \(x\) 的方案数。同一行有相邻 x&(x<<1)
,两行有相邻 x&y
。
代码:
#define ll long long
const int _mxn=12+2,_mod=1e8;
int n,m,a[_mxn];
int f[_mxn][1<<_mxn];//f[i][x]: i-th line state x
int main()
{
___();
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=0;j<m;j++)
{
int x;
cin>>x;
a[i]|=(!x)<<j;//处理一下方便判断
}
for(int x=0;x<(1<<m);x++)//枚举所有状态
if(!(x&x<<1)&&!(x&a[1]))//不相邻且都在能种的格子
f[1][x]=1;
for(int i=2;i<=n;i++)
{
for(int x=0;x<(1<<m);x++)//枚举所有状态
{
if((x&x<<1)||(x&a[i]))
continue;
for(int y=0;y<(1<<m);y++)//枚举上一行状态
{
if((y&y<<1)||(y&a[i-1])||(x&y))
continue;
(f[i][x]+=f[i-1][y])%=_mod;//累加
}
}
}
int ans=0;
for(int x=0;x<(1<<m);x++)
if(!(x&x<<1)&&!(x&a[n]))
(ans+=f[n][x])%=_mod;
cout<<ans<<endl;
return 0;
}
D
题意
有一个大学有 \(n\) 栋宿舍楼和 \(n-1\) 条路组成一棵边带权的树,第 \(i\) 栋宿舍楼住了 \(c_i\) 个人。现在要在某个宿舍楼设立一个快递站点,不便指数定义为 \(\sum_{i=1}^{n}d_i\times c_i\),其中 \(d_i\) 为 \(i\) 号宿舍楼到快递站点的距离。需要最小化不便指数,并输出最小值。
题解
换根 DP。先以 \(1\) 为根节点跑一遍 dfs 维护一堆东西:
- \(f_i\):\(i\) 子树的不便指数和。
- \(d_i\):\(i\) 到 \(1\) 的距离。
- \(s_i\):\(i\) 子树的点权和。
- \(w_i\):\(i\) 到父亲的边权。
设 \(g_u\) 为剖去 \(u\) 子树以 \(u\) 的父亲为根的答案。对于每个点 \(u\),枚举儿子 \(v\),有状态转移方程:
最终答案即为 \(\min_{i=1}^{n}(f_i-(d_i-w_i)\times s_i+g_i)\)。
代码:
#define ll long long
const int _mxn=1e5+5;
struct graph
{
typedef ll w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g;
int n;
ll c[_mxn];
ll f[_mxn],dis[_mxn],s[_mxn],wf[_mxn];
void dfs1(int u,int fa)
{
f[u]=dis[u]*c[u];
s[u]=c[u];
for(auto it:g.g[u])
{
if(it.v==fa)
continue;
dis[it.v]=dis[u]+it.w;
wf[it.v]=it.w;
dfs1(it.v,u);
s[u]+=s[it.v];
f[u]+=f[it.v];
}
}
ll ff[_mxn];//剖去 u 子树以 u 的 fa 为根的答案
void dfs(int u,int fa)
{
for(auto it:g.g[u])
{
if(it.v==fa)
continue;
int v=it.v;
ff[v]=f[u]-f[v]-dis[u]*(s[u]-s[v])+ff[u]+wf[u]*(s[1]-s[u]);
dfs(v,u);
}
}
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
cin>>c[i];
for(int i=1;i<n;i++)
{
int u,v;
ll w;
cin>>u>>v>>w;
g.add(u,v,w),g.add(v,u,w);
}
dfs1(1,0);
dfs(1,0);
ll ans=1e18;
for(int i=1;i<=n;i++)
{
ans=min(ans,f[i]-(dis[i]-wf[i])*s[i]+ff[i]);
// cout<<f[i]<<" "<<ff[i]<<" "<<dis[i]<<" "<<wf[i]<<" "<<s[i]<<" "<<f[i]-(dis[i]-wf[i])*s[i]+ff[i]<<endl;
}
cout<<ans<<endl;
return 0;
}
Day 4
Tarjan 求强连通分量(SCC)
vector<vector<int> > sccs;
int dfn[_mxn],low[_mxn],idx=0;
bool in[_mxn];
stack<int> st;
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
st.push(u);
in[u]=true;
for(int v:g[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
vector<int> scc;
while(114514<1919810)
{
int t=st.top();st.pop();
in[t]=false;
scc.push_back(t);
if(t==u)
break;
}
sccs.push_back(scc);
}
}
//主函数 i=1~n 执行 tarjan(i)
Day 4 模拟赛
A
题意
有 \(n\) 个集装箱垂直堆叠,第 \(i\) 个集装箱重量 \(w_i\),抗压程度 \(s_i\),危险指数为上方除自己的集装箱重量之和减抗压指数。可以改变堆叠顺序,使危险指数最大的集装箱危险指数尽量小。
题解
贪心。把每个集装箱按从下往上 \(w_i+s_i\) 升序排列就行了。正确性证明先咕着。
代码:
#define ll long long
const int _mxn=50000+5;
struct box
{
int w,s;
bool operator<(box x) const {return w+s<x.w+x.s;}
}a[_mxn];
int n;
int main()
{
___();
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].w>>a[i].s;
sort(a+1,a+n+1);
int sw=0,ans=-2e9;
for(int i=1;i<=n;i++)
{
ans=max(ans,sw-a[i].s);
sw+=a[i].w;
}
cout<<ans<<endl;
return 0;
}
B
题意
有 \(n\) 个节点,每个节点初始有一个粒子,每单位时间会往一个节点(可能是自己)发射粒子,求始终都会有粒子的节点数。
题解
可以直接拓扑排序,排不了的那些点就在环里。但是赛时我没这么写。
或者求强连通分量,因为每个点出度都为 \(1\),所以强连通分量一定是环,把点数 \(>1\) 的强连通分量和自环都加上就行了。
代码:
#define ll long long
const int _mxn=100000+5;
int n;
vector<int> g[_mxn];
void add(int u,int v){g[u].push_back(v);}
vector<vector<int> > sccs;
int dfn[_mxn],low[_mxn],idx=0;
bool in[_mxn];
stack<int> st;
void tarjan(int u)//tarjan 求强连通分量
{
dfn[u]=low[u]=++idx;
st.push(u);
in[u]=true;
for(int v:g[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
vector<int> scc;
while(114514<1919810)
{
int t=st.top();st.pop();
in[t]=false;
scc.push_back(t);
if(t==u)
break;
}
sccs.push_back(scc);
}
}
int main()
{
___();
cin>>n;
int ans=0;
for(int i=1,a;i<=n;i++)
{
cin>>a;
if(i==a)//自环提前加
ans++;
else
add(i,a);
}
for(int i=1;i<=n;i++)
tarjan(i);
for(auto scc:sccs)
{
if(scc.size()>1)
ans+=scc.size();
}
cout<<ans<<endl;
return 0;
}
C
题意
有 \(n\) 座城市,\(m\) 条双向航班,每条航班有一个花费。如果此时坐花费为 \(a\) 的航班前坐了花费为 \(b\) 的航班,则这次航班花费 \(\min(a,b)\)。求从 \(1\) 到 \(n\) 的最小花费。
题解
设 \(dis_{i,k}\) 为经过航班最小花费为 \(k\) 的最短路,然后跑 dijkstra 即可。
代码:
#define ll long long
const int _mxn=500+5,_mxm=125000+5;
struct graph
{
typedef int w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g;
int n,m;
int dis[_mxn][_mxm];//dis[i][k] 经过的最小代价 k 的最短路
vector<int> c;
int t;
map<int,int> mp;
void dijkstra(int s)
{
memset(dis,0x3f,sizeof(dis));
dis[s][t-1]=0;
typedef graph::node node;
typedef pair<node,int> pni;
priority_queue<pni,vector<pni>,greater<pni> > q;
q.push(make_pair(node(s,0),t-1));
while(!q.empty())
{
pni nw=q.top();q.pop();
int u=nw.first.v,d=nw.first.w,k=nw.second;
if(u==n)
break;
if(d>dis[u][k])
continue;
for(auto it:g.g[u])
{
int v=it.v;
ll w=min(it.w,k);
if(dis[v][w]>d+c[w])
{
dis[v][w]=d+c[w];
q.push(make_pair(node(v,dis[v][w]),w));
}
}
}
}
int main()
{
___();
cin>>n>>m;
c.push_back(1e6);
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
g.add(u,v,w),g.add(v,u,w);
c.push_back(w);
}
sort(c.begin(),c.end()),c.erase(unique(c.begin(),c.end()),c.end());
t=c.size();
for(int i=0;i<t;i++)
mp[c[i]]=i;
for(int i=1;i<=n;i++)
for(auto &it:g.g[i])
it.w=mp[it.w];
dijkstra(1);
int ans=1e9;
for(int i=0;i<t;i++)
ans=min(ans,dis[n][i]);
cout<<ans<<endl;
return 0;
}
D
题意
X 市由 \(n\) 个交通枢纽组成,有 \(m\) 条双向道路连接,每条道路有一个花费,所有市民每天下班后都要前往位于枢纽 \(n\) 的中央交通站换乘回家。有 \(k\) 个便利店位于一些枢纽,并有一个吸引力值,如果选择光顾便利店,那么回家花费会减少该便利店的吸引力值。市民们会在回家路上光顾至多一个便利店。现在询问每个枢纽的市民是否能在回家路上找到能使回家花费小于等于不光顾便利店的便利店。
题解
设 \(dis_{i,0}\) 为 \(n\) 到 \(i\) 不经过便利店的最小花费,\(dis_{i,1}\) 为经过便利店的最小花费。跑一遍 SPFA 即可。不过对于 \(dis_{i,0}\to dis_{i,1}\) 的转移,如果 \(i\) 有便利店,那么 \(dis_{i,1}=\min(dis_{i,1},dis_{i,0}-y_i)\)(其中 \(y_i\) 为 \(i\) 处便利店的吸引程度)。最后对于 \(1\le i<n\),判断是否 \(dis_{i,1}\le dis_{i,0}\) 即可。
代码:
#define ll long long
const int _mxn=5e4+5;
struct graph
{
typedef ll w_type;
struct node
{
int v;
w_type w;
node(){}
node(int _v,w_type _w):v(_v),w(_w){}
bool operator<(node x) const {return w<x.w;}
bool operator>(node x) const {return w>x.w;}
};
struct edge
{
int u,v;
w_type w;
edge(){}
edge(int _u,int _v,w_type _w):u(_u),v(_v),w(_w){}
bool operator<(edge x) const {return w<x.w;}
bool operator>(edge x) const {return w>x.w;}
};
vector<node> g[_mxn];
vector<edge> e;
void add(int u,int v,w_type w)
{
g[u].push_back(node(v,w));
e.push_back(edge(u,v,w));
}
}g,gc;
int n,m,k;
ll y[_mxn];
ll dis[_mxn][2];
bool in[_mxn][2];
void spfa()
{
memset(dis,0x3f,sizeof(dis));
memset(in,false,sizeof(in));
dis[n][0]=0;
typedef pair<int,int> pii;
queue<pii> q;
q.push(make_pair(n,0));
in[n][0]=true;
while(!q.empty())
{
pii nw=q.front();q.pop();
int u=nw.first,t=nw.second;
in[u][t]=false;
if(!t&&y[u])//没到且这里有便利店
{
if(dis[u][1]>dis[u][0]-y[u])
{
dis[u][1]=dis[u][0]-y[u];
if(!in[u][1])
{
q.push(make_pair(u,1));
in[u][1]=true;
}
}
}
for(auto it:g.g[u])
{
int v=it.v;
ll w=it.w;
if(dis[v][t]>dis[u][t]+w)
{
dis[v][t]=dis[u][t]+w;
if(!in[v][t])
{
q.push(make_pair(v,t));
in[v][t]=true;
}
}
}
}
}
int main()
{
___();
cin>>n>>m>>k;
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
g.add(u,v,w),g.add(v,u,w);
}
while(k--)
{
int x;
ll yt;
cin>>x>>yt;
y[x]=max(y[x],yt);
}
spfa();
for(int i=1;i<n;i++)
cout<<(dis[i][1]<=dis[i][0]?"YES":"NO")<<endl;
return 0;
}
Day 5
线段树
const int _mxn=1e5+5;
int n,a[_mxn];
struct segtree
{
struct node
{
int l,r;
ll dat;
ll add;
int len(){return r-l+1;}
}tr[_mxn<<2];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
void pushup(int p)
{
tr[p].dat=tr[ls(p)].dat+tr[rs(p)].dat;
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].add=0;
if(l==r)
{
tr[p].dat=a[l];
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
pushup(p);
}
void _upd(int p,ll add)
{
tr[p].dat+=add*tr[p].len();
tr[p].add+=add;
}
void pushdown(int p)
{
_upd(ls(p),tr[p].add);
_upd(rs(p),tr[p].add);
tr[p].add=0;//记得清零 tag
}
void update(int p,int l,int r,ll k)
{
if(l<=tr[p].l&&tr[p].r<=r)
{
_upd(p,k);
return;
}
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)
update(ls(p),l,r,k);
if(r>mid)
update(rs(p),l,r,k);
pushup(p);
}
ll query(int p,int l,int r)
{
if(l<=tr[p].l&&tr[p].r<=r)
return tr[p].dat;
pushdown(p);
int mid=(tr[p].l+tr[p].r)>>1;
ll res=0;
if(l<=mid)
res+=query(ls(p),l,r);
if(r>mid)
res+=query(rs(p),l,r);
return res;
}
}tr;
树链剖分
Day 5 模拟赛
A
题意
给定 \(n,x,y(1\le n,x,y\le100)\),求 \(1\sim n\) 中恰好能被 \(x,y\) 其中一个整除的数的和。
题解
枚举并判断即可。代码不放了。
B
题意
给定一个长 \(2^n\) 的序列,进行 \(n\) 次操作,第 \(i\) 次操作:
- 若 \(i\bmod2=0\),则对于每个 \(k\),
D
题意
题解
\(K\ge2\),最长环游一定包含 \(K=1\) 时选取的点