2024 平邑一中集训 笔记(下)
Day10
考试
T3
形式化题意,给定 \(n,m\),求\(\sum^n_{i=1} \sum^m_{j=1} \displaystyle \begin{pmatrix}n\\i\\\end{pmatrix}\displaystyle \begin{pmatrix}i\\j\\\end{pmatrix}\)
推式子:
\[\sum^n_{i=1} \sum^m_{j=1} \displaystyle \begin{pmatrix}n\\i\\\end{pmatrix}\displaystyle \begin{pmatrix}i\\j\\\end{pmatrix}
\]
\[\Longrightarrow\sum^n_{i=1}
\sum^m_{j=1}\displaystyle\begin{pmatrix}
n\\j\\\end{pmatrix}\displaystyle \begin{pmatrix}n-j\\i-j\\
\end{pmatrix}
\]
\[\Longrightarrow\sum^n_{j=1} \sum^m_{i=1}\displaystyle\begin{pmatrix}
n\\i\\\end{pmatrix}\displaystyle \begin{pmatrix}
n-i\\j-i\\\end{pmatrix}
\]
\[\Longrightarrow\sum^m_{i=1} \displaystyle\begin{pmatrix}
n\\i\\\end{pmatrix}
\sum^n_{j=1} \displaystyle \begin{pmatrix}n-i\\
j-i\\\end{pmatrix}
\]
\[\Longrightarrow\sum^m_{i=1} \displaystyle\begin{pmatrix}
n\\i\\\end{pmatrix}\displaystyle \begin{pmatrix}
\displaystyle \begin{pmatrix}
n-i\\1-i\\\end{pmatrix}+\displaystyle \begin{pmatrix}n-i\\2-i\\
\end{pmatrix}+\dots+\displaystyle \begin{pmatrix}
n-i\\n-i\\\end{pmatrix}\end{pmatrix}
\]
\[\Longrightarrow\sum^m_{i=1} \displaystyle\begin{pmatrix}n\\i\\
\end{pmatrix}2^{n-i}
\]
再根据\(C^{i+1}_n=\frac{n-i}{i+1} \times C^i_n\)递推组合数即可
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=5e6+5;
const ll mod=1e9+7;
ll n,m;
ll ans=0;
ll f[N];//逆元数组
ll ksm(ll a,ll b){//快速幂
ll ans=1;
for(;b;b>>=1){
if(b&1) ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
void dt(){//逆元递推式
f[1]=1;
for(int i=2;i<=m;i++) f[i]=(mod-mod/i)*f[mod%i]%mod;
}
signed main()
{
cin>>n>>m;
dt();
ll zhs=ksm(2,n-1);//代表2^(n-i)
ll now=n;//代表代表C^i_n
for(int i=1;i<=m;i++){
ans=(ans+zhs*now%mod)%mod;//更新答案
zhs=zhs*f[2]%mod;//将原来的2^(n-i)变为2^(n-i)*2^(-1)=2^(n-i-1)
now=now*(n-i)%mod*f[i+1]%mod;//将原来的C^i_n 变为 C^(i+1)_n ,具体见公式
}
cout<<ans;
return 0;
}
Day11
考试,最遗憾的一集
- T1暴力打满如果不动代码能靠暴力水过去95pts的,但是结束前10分钟非得写个什么都不是的正解,95pts->35pts
- T2写出正解了,但是线下评测文件名写错,100pts->0pts
- T4暴力能拿40pts的,但是脑残把
a[i],a[j]写成了i,j。40pts->0pts - 265pts->65pts,狂挂200多分
rk10->rk30+ - 我以后再写不确定的代码交上去我就是傻逼
赛后一看全是傻逼题,不调了
Day13
T2
给出一颗树, 求满足以下条件的三元点集个数:
假设点集中有节点 \(a,b,c\) 则以 \(dis(a,b)\),\(dis(b,c)\),\(dis(a,c)\) 为三边长的三角形存在。
\(dis(x,y)\) 表示节点 \(x\) 和 \(y\) 在树上的距离。
- 注意到当三个点在一条链上时必定不能构成三角形,因此可以先求出总方案数,在求出不合法方案数,再用总方案数-不合法方案数即为答案
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,siz[N],ans=0,cnt=0;
//siz[i]代表以i为根的所有子树大小之和(包括i)
vector<int> tu[N];
void dfs(int x,int lst){
siz[x]=1;
for(int i=0;i<tu[x].size();i++){
int u=tu[x][i];
if(u!=lst){
dfs(u,x);
cnt+=siz[u]*(n-1-siz[u]);//当前子树节点个数*除该子树外所有节点的个数(包括x的另外一些子树)
siz[x]+=siz[u];//x的子树大小更新
}
}
cnt+=(siz[x]-1)*(n-siz[x]);//由于从外面到里面的方案加了一次,子树内部的总方案数加2次,因此要将总子树内部的点与外界的方案再加一次,最终将结果除二即可
}
signed main()
{
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
tu[u].push_back(v);
tu[v].push_back(u);
}
int ans=n*(n-1)*(n-2)/6;
dfs(1,0);
cout<<ans-cnt/2;
return 0;
}
T3
- 注意到喷泉向下留到的第一个圆盘一定是第一个比它大的圆盘,由此考虑单调栈预处理
- 再采取倍增加快速率,设 \(f[i][j]\) 表示从 \(i\) 节点向下第 \(2^j\) 个的将能留到的圆盘,\(f[i][j]=f[f[i][j-1]][j-1]\) 递推即可
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,q,d[N],c[N];
int nxt[N][20],qzh[N][20];//qzh[i][j]代表从i向下能跳到的1-j的圆盘的前缀和
stack<int> st;
void prework(){
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)<=n;i++){
nxt[i][j]=nxt[nxt[i][j-1]][j-1];
qzh[i][j]=qzh[nxt[i][j-1]][j-1]+qzh[i][j-1];
}
}
}
int query(int r,int v){
if(c[r]>=v) return r;
v-=c[r];
for(int i=18;i>=0;i--){
if(nxt[r][i]!=0&&qzh[r][i]<v){
v-=qzh[r][i];
r=nxt[r][i];
}
}
return nxt[r][0];
}
signed main()
{
cin>>n>>q;
c[0]=1e10;
for(int i=1;i<=n;i++){
cin>>d[i]>>c[i];
}
for(int i=n;i>=1;i--){
while(st.size()&&d[st.top()]<=d[i]) st.pop();
if(st.size()){
nxt[i][0]=st.top();
qzh[i][0]=c[st.top()];
}
st.push(i);
}
prework();
for(int i=1;i<=q;i++){
int r,v;
cin>>r>>v;
cout<<query(r,v)<<"\n";
}
return 0;
}
Day15
Tarjan
Tarjan求强连通分量
若在图中存在一个点集 \(|S|\) ,对于任意 \((i,j) \in |S|\) ,\(i,j\) 都可以互相到达,则称 \(|S|\) 为图中的一个强连通分量
例题:B3609 强连通分量
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
vector<int> tu[N];
bool vis[N];
int cnt=0;
int dfn[N],low[N];
stack<int> st;
int all=0;
vector<int> ans[N];
vector<int> anss[N];
void tarjan(int x){
cnt++;
dfn[x]=low[x]=cnt;
vis[x]=1;
st.push(x);
for(int i=0;i<tu[x].size();i++){
int v=tu[x][i];
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(dfn[x]==low[x]){
int y;
all++;
while(x!=y){
y=st.top();
vis[y]=0;
st.pop();
ans[all].push_back(y);
}
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
tu[x].push_back(y);
}
for(int i=1;i<=n;i++){
if(dfn[i]==0){
tarjan(i);
}
}
cout<<all<<"\n";
for(int i=1;i<=all;i++){
sort(ans[i].begin(),ans[i].end());
for(int j=0;j<ans[i].size();j++){
cout<<ans[i][j]<<' ';
}
}
return 0;
}
- 求出所有强连通分量然后找到最大的输出即可
Tarjan求割点
去掉一个点后,图不再联通,则称该点为割点
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
vector<int> tu[N];
int ans[N],top=0;
int dfn[N],low[N];
int now=0,ro;
void tarjan(int x){
int topp=0;
dfn[x]=low[x]=++now;
for(int i=0;i<tu[x].size();i++){
int v=tu[x][i];
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]){
topp++;
if(x!=ro||topp>1){
ans[x]=1;
}
}
}
else low[x]=min(low[x],dfn[v]);
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
tu[u].push_back(v);
tu[v].push_back(u);
}
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++){
if(dfn[i]==0){
ro=i;
tarjan(i);
}
}
top=0;
for(int i=1;i<=n;i++) if(ans[i]) top++;
cout<<top<<'\n';
for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<' ';
return 0;
}
- 观察易得,当删除点为普通点时,对答案所做贡献为 \(2 \times (n-1)\)
- 当删除点为割点时,对于每个连通块,答案为 \(size \times (n-size)\)
- 在割点分割的若干连通快里放两个救援出口,求方案即可

浙公网安备 33010602011771号