關於生成式 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;
}
}
這是什麼
初音未來可愛捏