关于生成式 AI 怎么才能让这个 b 学会断句 我目前的方案是,把逗号和句号单独作为一个特殊词汇看待,也统计到词频里,该断句的时候就断
表扬这次的题解,写的很清楚
A.排列最小生成树
- 总存在一颗生成树使得树上最大边权值小于 \(n\)
考虑直接连接序列里的所有 \((i,i+1)\),因为 \(|a_i-a_{i+1}|\lt n\)(由排列的性质),因此 \(w_{\max}\lt((i+1)-i)\times n=n\)
所以我们直接考虑连权值小于 \(n\) 的边即可
- 一条边 \((i,j)\lt n\),则 \(|i-j|\lt \sqrt{n}\) 或 \(|a_i-a_j|\lt\sqrt{n}\)
考虑反证,设 \(|i-j|\ge \sqrt{n}\) 且 \(|a_i-a_j|\ge \sqrt{n}\),考虑最小的情况也有 \(\sqrt{n}\times \sqrt{n}=n\ge n\),因此原命题成立
所以我们只需要考虑使得 \(|i-j|\lt \sqrt{n}\) 或 \(|a_i-a_j|\lt\sqrt{n}\) 的节点即可
因为是排列,可以提前统计每个值所在的位置,然后直接通过值或者位置加减来找对应的点
枚举的这里有个优化(没啥大用)
- 由于是无序数对,钦定里层的变量小于外层变量,这样可以少一半枚举量和空间浪费
找到了以后直接跑最小生成树就行了
但是直接这么跑复杂度是 \(n\sqrt{n}\log n\alpha\) 的,还挂大常数,显然过不去
刚才我们已经证明了,我们找到的值的值域在 \(n\) 之内,因此我们直接对值域开桶,每次找到数对就存到对应的桶里,这样就能避免排序
复杂度 \(n\sqrt{n}\alpha\)
#include<bits/stdc++.h>
using namespace std;
struct dsu{
int fa[50001];
void clear(int n){
for(int i=1;i<=n;++i){
fa[i]=i;
}
}
int find(int id){
if(id==fa[id]) return id;
return fa[id]=find(fa[id]);
}
bool connect(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return false;
fa[fx]=fy;
return true;
}
}dsu;
int n;
int a[50001];
inline int sol(int i,int j){
return abs(i-j)*abs(a[i]-a[j]);
}
struct node{
int i,j;
};
vector<node>q[50001];
int pos[50001];
int vis[50001];
int main(){
int st=scanf("%d",&n);
for(int i=1;i<=n;++i){
st=scanf("%d",&a[i]);
pos[a[i]]=i;
}
dsu.clear(n);
int tmp=sqrt(n);
for(int i=1;i<=n;++i){
for(int j=max(1,i-tmp);j<i;++j){
if(sol(i,j)<=n){
q[sol(i,j)].push_back({i,j});
}
if(sol(pos[i],pos[j])<=n){
q[sol(pos[i],pos[j])].push_back({pos[i],pos[j]});
}
}
}
long long tot=0,ans=0;
for(int i=1;i<=n;++i){
for(node u:q[i]){
if(tot==n-1) break;
if(dsu.connect(u.i,u.j)){
ans+=i;
tot++;
}
}
}
cout<<ans<<endl;
}
B.卡牌游戏
先考虑互质怎么做
n=3 m=4
n 1 2 3 1 2 3 1 2 3 1 2 3
m 1 2 3 4 1 2 3 4 1 2 3 4
通过找规律可以发现,\(m_i=1\) 的所有位置中,对应的 \(n_i=1,2,3\) 恰好都出现过了一遍,其他的 \(m_i\) 也是同理,因此我们直接对 \(n\) 维护有序数组,对每个 \(m_i\),既然它们面对的对手都是一样的,那么也一样处理,在 \(n\) 的有序数组里二分查找大于,小于,等于 \(m_i\) 的数即可
那么不互质怎么做
n=4 m=6 gcd(n,m)=2
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6 1 2 3 4 5 6
可以发现,\(m_i=1,3,5\) 对应了 \(n_i=1,3\),\(m_i=2,4,6\) 对应了 \(n_i=2,4\)
- 如果 \(m_i\equiv n_j\pmod {\text{gcd}(n,m)}\),则 \(m_i\) 的对手中有 \(n_j\)
这里感性理解比较好理解,这里 \(i\) 和 \(j\) 可以理解成两个数组下标差(偏移量),如果两个数初始偏移量为 \(dx\),每次偏移量均会增加 \(|n-m|\),设 \(n=k_1\text{gcd}(n,m),m=k_2\text{gcd}(n,m)\),进行 \(g\) 轮后的偏移量为 \(g\times |n-m|+dx=g|k_1-k_2|\text{gcd}(n,m)+dx\),可以发现都是在模 \(\text{gcd}(n,m)\) 的意义下同余的
因此可以拆开,将 \(i\mod \text{gcd}(n,m)\) 的数分为同一组,直接像上面互质的情况那样分别跑就行了
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[100001],b[100001];
vector<int>v[100001];
int ans[3];
int gcd;
signed main(){
scanf("%lld %lld",&n,&m);
gcd=__gcd(n,m);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
v[i%gcd].push_back(a[i]);
}
for(int i=0;i<=gcd-1;++i) sort(v[i].begin(),v[i].end());
for(int i=1;i<=m;++i){
scanf("%lld",&b[i]);
ans[0]+=tmp*gcd;ans[1]+=tmp2*gcd;ans[2]+=(v[i%gcd].size()-tmp-tmp2)*gcd;
}
cout<<ans[1]<<'\n'<<ans[0]<<'\n'<<ans[2]<<'\n';
}
C.比特跳跃
\(S=1\)
考虑怎么从 \(1\) 完全不花费跳到任意点
- 如果目标点的二进制末尾是 \(0\),直接从 \(1\) 跳就不会有任何花费
- 如果目标点的二进制末尾是 \(1\),那么将其取反,反串的二进制末尾是 \(0\),可以从 \(1\) 跳到其反串,再从反串调到目标点,均不需要花费
但是唯一的特例是形如 \(11111\cdots\) 的点(发现只有当 \(n=2^k\) 的时候,\(2^k-1\) 会出现这种情况,否则可以直接从高位上的点跳走),因为它的反串是 \(0\),而我们并没有 \(0\) 这个节点,此时我们可以采用下面两种方式跳到这个点
- 直接从 \(1\) 跳过去
- 不用花费地跳到相邻点,通过相邻的连边直接走到目标点
取最小值即可
\(S=2\)
关于异或,可以考虑拆位
- 枚举 \(i\),每次我们只异或 \(i\) 的其中一位,并且将异或结果与 \(i\) 连边(因为是无向图,为了避免重复连边,可以只枚举 \(i\) 中为 \(1\) 的二进制位,实测不这么做会炸空间)
- 直接跑最短路即可
\(S=3\)
与
- 如果 \(i\) 不是 \(j\) 的子集,\(1\rightarrow i\rightarrow j\) 一定不比 \(1\rightarrow j\) 优
因为你从 \(1\) 到 \(i\) 的贡献是 \(1\operatorname{or}i\),\(i\) 到 \(j\) 的贡献是 \(i\operatorname{or}j\),这两个加和一定包含了 \(1\operatorname{or}j\),并且还会多出来点
因此这么做
- 先跑一遍最短路
- 对每个 \(i\) 枚举其子集,尝试松弛 \(i\)
- 再跑一遍最短路
这个枚举子集的复杂度太大,具体实现的时候,建一个数组 \(f_i\) 表示 \(i\) 及其子集的最小 \(dis\),然后枚举和 \(i\) 只差一个二进制位,并且还是 \(i\) 的子集的数(这些数一定比 \(i\) 小,如果你从小到大枚举 \(i\) 就可以保证这些数一定先被更新过了),用这些数的最小值对 \(i\) 进行松弛,同时记得也用这些数更新 \(f_i\),这样才能实现我们枚举所有子集的效果
要注意的点
- 第二遍最短路前先把要松弛的点入队(或者你懒得判断直接入所有点也可以)
- 初值,\(f_1=0\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,s,k;
struct edge{
int to,w;
};
vector<edge>e[200001];
int dis[200001];
bool vis[200001];
struct node{
int id,dis;
bool operator <(const node&A)const{
return dis>A.dis;
}
};
priority_queue<node>q;
namespace subtask1{
void dij(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
q.push({s,dis[s]});
while(!q.empty()){
node u=q.top();q.pop();
if(vis[u.id]) continue;
vis[u.id]=true;
for(edge i:e[u.id]){
if(dis[i.to]>dis[u.id]+i.w){
dis[i.to]=dis[u.id]+i.w;
q.push({i.to,dis[i.to]});
}
}
}
}
void main(){
for(int i=1;i<=m;++i){
int x,y,z;int st=scanf("%lld %lld %lld",&x,&y,&z);
e[x].push_back({y,z});
e[y].push_back({x,z});
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
if(s==1){
e[i].push_back({j,k*(i&j)});
e[j].push_back({i,k*(i&j)});
}
if(s==2){
e[i].push_back({j,k*(i^j)});
e[j].push_back({i,k*(i^j)});
}
if(s==3){
e[i].push_back({j,k*(i|j)});
e[j].push_back({i,k*(i|j)});
}
}
}
dij(1);
for(int i=2;i<=n;++i){
cout<<dis[i]<<" ";
}
cout<<endl;
}
}
bool ispowerof2(int x){
if(x==1) return true;
if(x%2!=0) return false;
return ispowerof2(x/2);
}
namespace subtask2{
void main(){
for(int i=2;i<=n;++i){
cout<<0<<" ";
}
cout<<endl;
}
}
namespace subtask3{
void main(){
for(int i=1;i<=m;++i){
int x,y,z;int st=scanf("%lld %lld %lld",&x,&y,&z);
e[x].push_back({y,z});
e[y].push_back({x,z});
}
int minn=0x7fffffff;
for(edge i:e[n]) minn=min(minn,i.w);
for(int i=2;i<=n-1;++i){
cout<<0<<" ";
}
cout<<min(minn,k)<<" ";
cout<<endl;
}
}
namespace subtask4{
void dij(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
q.push({s,dis[s]});
while(!q.empty()){
node u=q.top();q.pop();
if(vis[u.id]) continue;
vis[u.id]=true;
for(edge i:e[u.id]){
if(dis[i.to]>dis[u.id]+i.w){
dis[i.to]=dis[u.id]+i.w;
q.push({i.to,dis[i.to]});
}
}
}
}
void main(){
for(int i=1;i<=m;++i){
int x,y,z;scanf("%lld %lld %lld",&x,&y,&z);
e[x].push_back({y,z});
e[y].push_back({x,z});
}
for(int i=1;i<=n;++i){
for(int j=0;j<=31;++j){
if((i&(1ll<<j))){
e[i].push_back({i^(1ll<<j),k*(1ll<<j)});
e[i^(1ll<<j)].push_back({i,k*(1ll<<j)});
}
}
}
dij(1);
for(int i=2;i<=n;++i){
cout<<dis[i]<<" ";
}
}
}
namespace subtask5{
int f[200001];
void dij(int s){
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
q.push({s,dis[s]});
while(!q.empty()){
node u=q.top();q.pop();
if(vis[u.id]) continue;
vis[u.id]=true;
for(edge i:e[u.id]){
if(dis[i.to]>dis[u.id]+i.w){
dis[i.to]=dis[u.id]+i.w;
q.push({i.to,dis[i.to]});
}
}
}
}
void dij2(int s){
memset(vis,0,sizeof vis);
dis[s]=0;
q.push({s,dis[s]});
while(!q.empty()){
node u=q.top();q.pop();
if(vis[u.id]) continue;
vis[u.id]=true;
for(edge i:e[u.id]){
if(dis[i.to]>dis[u.id]+i.w){
dis[i.to]=dis[u.id]+i.w;
q.push({i.to,dis[i.to]});
}
}
}
}
void main(){
for(int i=1;i<=m;++i){
int x,y,z;scanf("%lld %lld %lld",&x,&y,&z);
e[x].push_back({y,z});
e[y].push_back({x,z});
}
for(int i=2;i<=n;++i){
e[1].push_back({i,k*(1ll|i)});
e[i].push_back({1,k*(1ll|i)});
}
dij(1);
memset(f,0x3f,sizeof f);
f[1]=0;
for(int i=2;i<=n;++i){
for(int j=0;j<=31;++j){
if((i&(1ll<<j))){
dis[i]=min(dis[i],f[i^(1ll<<j)]+k*i);
f[i]=min(dis[i],f[i^(1ll<<j)]);
}
}
}
for(int i=2;i<=n;++i){
q.push({i,dis[i]});
}
dij2(1);
for(int i=2;i<=n;++i){
cout<<dis[i]<<" ";
}
}
}
signed main(){
freopen("jump.in","r",stdin);
freopen("jump.out","w",stdout);
// freopen("sample/jump/ex6.in","r",stdin);
// freopen("out.out","w",stdout);
int st=scanf("%lld %lld %lld %lld",&n,&m,&s,&k);
if(n<=800 and m<=800){
subtask1::main();
return 0;
}
if(s==1 and ispowerof2(n+1)==false){
subtask2::main();
return 0;
}
if(s==1){
subtask3::main();
return 0;
}
if(s==2){
subtask4::main();
return 0;
}
if(s==3){
subtask5::main();
return 0;
}
}
这是什么
初音未来可爱捏