T1
题意
将给定的正整数 \(n\),表示为只包含数字 \(1\)
分析
看起来是一道构造题,令 \(a_i\) 表示由 \(i\) 个 \(1\) 组成的正整数,注意到每个 \(a_i\) 的使用次数都可以通过增加 \(a_{i+1}\) 的使用次数来减少,暴力 \(dfs\) 是否利用 \(a_{i+1}\) 优化即可,考场上想出正解,期望得分 \(100pt\),实际得分 \(100pt\)。
Code
#include<iostream>
#include<vector>
#include<algorithm>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
long long n,ans=0x3f3f3f3f3f3f3f3f;
vector<long long>num={0,1,11,111,1111,11111,111111,1111111,11111111,111111111,1111111111,11111111111,111111111111,1111111111111,11111111111111,111111111111111,1111111111111111};
void Dfs(int dep,long long t,long long sum)
{
if(!dep){ans=min(ans,(t?0x3f3f3f3f3f3f3f3f:sum));return;}
int tmp=lower_bound(num.begin(),num.end(),t)-num.begin();
Dfs(dep-1,(num[tmp]-t)%num[dep],sum+(num[tmp]-t)/num[dep]*dep+tmp),Dfs(dep-1,t%num[dep],sum+t/num[dep]*dep);
}
int main()
{
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
IOS,cin>>n,Dfs(16,n,0),cout<<ans<<'\n';
return 0;
}T2
题意
给定一棵 \(n\) 个节点的树,\(m\)
分析
删边不好写,考虑并查集经典应用,把操作离线下来,删边改成加边,而询问操作其实是在问 \(x\)
那么问题转向如何在合并操作时维护集合的直径,注意到合并后新集合的直径端点必然也是原本两集合中的直径端点,故只需要对这四个点两两求路径长度即可。求路径长度必然要使用 \(LCA\),这里可以直接使用原树上的 \(LCA\),原因是树上路径唯一,只要 \(x,y\) 两点连通,那二者在任意时刻的 \(LCA\) 一定是原树上的 \(LCA\)。考场上写的 \(O(1)\) 修改 \(dfs\) 暴力查询加上链的特判,期望得分 \(40pt\),实际得分 \(80pt\),猜测可能是因为删边操作让每次 \(dfs\) 需要遍历的连通块大小变小,跑不满 \(O(n)\),当然可能也有数据过水的原因。
Code
#include<iostream>
#include<utility>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N=5e5+5;
const int LogN=20;
int n,m,head[N],nxt[N<<1],to[N<<1],cnt=0,dep[N],father[N][LogN],leader[N],ans[N];
bool vis[N];
pair<int,int>Edge[N];
struct Node
{
int pa,pb,dia;
}Tree[N];
struct Data
{
int opt,x;
}Ques[N];
void Add(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void Dfs(int u,int fa)
{
dep[u]=dep[fa]+1,father[u][0]=fa;
for(int i=1;i<LogN;i++)
father[u][i]=father[father[u][i-1]][i-1];
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa) continue;
Dfs(v,u);
}
}
int LCA(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=LogN-1;i>=0;i--)
if(dep[father[u][i]]>=dep[v])
u=father[u][i];
if(u==v) return u;
for(int i=LogN-1;i>=0;i--)
if(father[u][i]!=father[v][i])
u=father[u][i],v=father[v][i];
return father[u][0];
}
int Dis(int u,int v)
{
return dep[u]+dep[v]-2*dep[LCA(u,v)];
}
int Find(int k)
{
if(leader[k]!=k) return leader[k]=Find(leader[k]);
return k;
}
void Merge(int u,int v)
{
int fu=Find(u),fv=Find(v);
leader[fu]=fv;
Node res=(Tree[fu].dia>Tree[fv].dia?Tree[fu]:Tree[fv]);
int maxx=Dis(Tree[fu].pa,Tree[fv].pa);
if(res.dia<=maxx)
res=(Node){Tree[fu].pa,Tree[fv].pa,maxx};
maxx=Dis(Tree[fu].pa,Tree[fv].pb);
if(res.dia<=maxx)
res=(Node){Tree[fu].pa,Tree[fv].pb,maxx};
maxx=Dis(Tree[fu].pb,Tree[fv].pa);
if(res.dia<=maxx)
res=(Node){Tree[fu].pb,Tree[fv].pa,maxx};
maxx=Dis(Tree[fu].pb,Tree[fv].pb);
if(res.dia<=maxx)
res=(Node){Tree[fu].pb,Tree[fv].pb,maxx};
Tree[fv]=res;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
IOS;
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
Add(u,v),Add(v,u);
Edge[i]={u,v};
}
for(int i=1;i<=m;i++)
{
cin>>Ques[i].opt>>Ques[i].x;
if(Ques[i].opt==1) vis[Ques[i].x]=true;
}
Dfs(1,0);
for(int i=1;i<=n;i++)
leader[i]=i,Tree[i]={i,i,0};
for(int i=1;i<n;i++)
if(!vis[i]) Merge(Edge[i].first,Edge[i].second);
for(int i=m;i>=1;i--)
{
if(Ques[i].opt==1) Merge(Edge[Ques[i].x].first,Edge[Ques[i].x].second);
else ans[i]=max(Dis(Ques[i].x,Tree[Find(Ques[i].x)].pa),Dis(Ques[i].x,Tree[Find(Ques[i].x)].pb));
}
for(int i=1;i<=m;i++)
if(Ques[i].opt==2)
cout<<ans[i]<<'\n';
return 0;
}T3
题意
给定正整数 \(n\),构造 \(n\) 个节点的无向连通图,令第 \(i\) 个点到点 \(1\) 的距离为 \(d_i\),满足 \(\forall i \in [1,n-1],d_i < d_n\),求满足条件的图的数量。
分析
考虑把 \(n\) 个点分布在 \(k\) 层上,第 \(i\) 层与点 \(1\) 的距离是 \(i\),第 \(0\) 层只有点 \(1\),第 \(k\) 层只有点 \(n\),这样就保证了 \(d_n\)
那么剩下的就是一个组合问题,把 \(n-2\) 个点分布至 \(k-1\) 层,保证每层不为空,且层与层之间至少有一条连边的连边方案数。由于层内连边不影响最短路径的长度,所以在这一点上不加以限制。设置状态 \(f_{s,x}\) 表示已经放了 \(s\) 个点,最后一层点数为 \(x\) 的方案数,那么考虑由 \(f_{s,x}\) 到 \(f_{s+y,y}\) 的转移,从剩下的点里选出 \(y\) 个点的方案数是 \(C_{n-2-s}^y\) 种,层内连边 \(2^{C_y^2}\) 种方案,两层之间共可以连 \(xy\) 条边,层间连边方案数 \(2^{xy}-1\) (保证至少连一条边),使用乘法原理计算。那么答案就是 \(\Sigma_{i=1}^{n-2} f_{n-2}^i \times (2^{n-2}-1)\)。考场上想到正解,期望得分 \(100pt\) 实际得分 \(100pt\)。
Code
#include<iostream>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0),cout.tie(0)
using namespace std;
const int MOD=1e9+7;
const int N=1005;
int n;
long long fac[N],inv[N],pow2[N],pow2C[N],a[N][N],f[N][N],ans=0;
long long quick_pow(long long a,long long b)
{
long long res=1,base=a;
while(b)
{
if(b&1) res=res*base%MOD;
base=base*base%MOD;
b>>=1;
}
return res;
}
void init(int maxx)
{
fac[0]=1;
for(int i=1;i<=maxx;i++)
fac[i]=fac[i-1]*i%MOD;
inv[maxx]=quick_pow(fac[maxx],MOD-2);
for(int i=maxx-1;i>=0;i--)
inv[i]=inv[i+1]*(i+1)%MOD;
pow2[0]=1;
for(int i=1;i<=maxx;i++)
pow2[i]=pow2[i-1]*2%MOD;
for(int i=0;i<=maxx;i++)
pow2C[i]=quick_pow(2,i*(i-1)/2);
for(int i=1;i<=maxx;i++)
{
long long base=(pow2[i]-1+MOD)%MOD;
a[i][1]=base;
for(int j=2;j<=maxx;j++)
a[i][j]=a[i][j-1]*base%MOD;
}
}
int main()
{
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
IOS;
cin>>n;
init(n-2);
f[0][1]=1;
for(int s=1;s<=n-2;s++)
{
for(int x=1;x<=s;x++)
{
long long tmp=0;
for(int y=1;y<=n-2;y++)
if(s-x>=0) tmp=(tmp+a[x][y]*f[s-x][y])%MOD;
f[s][x]=pow2C[x]*inv[x]%MOD*tmp%MOD;
}
}
for(int i=1;i<=n-2;i++)
ans=(ans+f[n-2][i])%MOD;
cout<<ans*fac[n-2]%MOD<<'\n';
return 0;
}T4
题意
定义序列大小的比较方式,对在 \(A,B\) 两个序列中出现的元素从小到大考虑,对于元素 \(x\),若其在 \(A\) 中出现的次数大于在 \(B\) 中出现的次数,则序列 \(A\) 更大,反之亦然。对于给定的长度为 \(n\) 的序列,求其排名为 \(k\)
分析
据说是道黑题,考场上写了个暴力排序,期望得分 \(40pt\),实际得分 \(40pt\),小数据范围给的分意外的多,看题解写的是随机二分,目前还没订正。
















