10.12離線賽
一、神經網路
資料:對於50%的資料,n∈[1,100]
對於100%的資料,n∈[1,200]
很簡單的一道題,但是隻看題目會有點誤解。題目中說神經若處於興奮狀態,下一秒向後發訊息。我理解成這個要按照時間來,用完後就有成平靜態。這樣的話U[i]就多減了,而且有問題,只有20分。
實際上是一個點的Ci要把所有他的輸入的Cj全部算出來才行。
這樣就有兩種方法:
1、按照拓撲序,一層一層算過來
2、反向,從輸出層往前,用一下記憶化搜尋。
我寫的是記憶化搜尋。這個比較簡單,只需反向存一下邊就行了。
二、最大字典序排列
資料:對於60%的資料,n∈[1,2000]
對於80%的資料,n∈[1,20000]
對於100%的資料,n∈[1,100000]
不看題就猜這是到dp題,這是慣例。然後猜錯了。幸好沒仔細想。
正解是貪心,每次從後面選出一個數值最大並且可以交換到這個位子的數。直接實現的話類似於氣泡排序,一位一位交換上來,這樣是n^2。但這道題直接這樣算不對,這樣寫有80分。
然後就要寫N log N 的了。log N 的不多,這裡用的是線段樹。
線段樹裡裝的是在[L,R]這個區間裡的最大值和這個區間裡還有幾個數沒被用過(即被交換到前面)。不用記錄最大值的位置嗎?因為是1-N的排列,只要起初記錄一下,然後再刪除就行了。
Code:
#include<bits/stdc++.h>
using namespace std ;
int A[100005],G[100005],Q[100005];//原陣列,每個數的位置,哪些數已輸出
struct node{int L,R,mx,cnt;}tree[400005];//左右,最大值,有多少個數
void Up(int p){
tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);
tree[p].cnt=tree[p*2].cnt+tree[p*2+1].cnt;
}
void Build(int L,int R,int p){
tree[p].L=L;tree[p].R=R;
if(L==R){
tree[p].mx=A[L];
tree[p].cnt=1 ;
return;
}
int mid=(L+R)/2;
Build(L,mid,p*2);Build(mid+1,R,p*2+1);
Up(p);
}
int Delete(int x,int p){//一邊刪除,一邊計算移動步數
if(tree[p].L==tree[p].R){
tree[p].mx=tree[p].cnt=0;
return 0;
}
int mid=(tree[p].L+tree[p].R)/2;
int del;
if(mid>=x)del=Delete(x,p*2);
else del=Delete(x,p*2+1)+tree[p*2].cnt;
Up(p);
return del;
}
int Query(int K,int p){
if(K<0)return 0;
if(tree[p].cnt<=K)return tree[p].mx;//大區間符合,直接返回
int Lr=Query(K,p*2);//先左邊,移動的步數少,
if(tree[p*2].cnt<K)return max(Lr,Query(K-tree[p*2].cnt,p*2+1));//行的話右邊,K要減一下是因為右邊的也要交換經過左邊
return Lr;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
G[A[i]]=i;Q[i]=1;//記錄位置,初始狀態
}
Build(1,n,1);
for(int i=1;i<=n;i++){
if(k<=0)break;
int res=Query(k+1,1);//單點查詢
printf("%d\n",res);
Q[G[res]]=0;
int del=Delete(G[res],1);
k-=del;//減去res的交換步數
}
for(int i=1;i<=n;i++)
if(Q[i])printf("%d\n",A[i]);//最後沒輸出的輸出
return 0;
}
三、航線規劃
資料:對於 30%的資料,n 的範圍[2,100],m 的範圍[1,500],Q 的範圍[1,100]
對於 60%的資料,n 的範圍[2,10000],m 的範圍[1,30000],Q 的範圍[1,20000],資料中沒有刪邊操作
對於 100%的資料,n 的範圍[2,30000],m 的範圍[1,100000],Q 的範圍[1,40000],資料保證任何時候圖都是連通,且沒有重邊和自環
又一道圖論神題。兼用前幾次考試最後一題的部分思想。
理一下知識點:
1、線段樹(可以用BIT代替)
2、路徑壓縮(並查集實現)
3、LCA(倍增)
4、dfs序(造樹)
5、求兩點之間的路徑上的值
這道題與前面的不一樣的地方是,建樹時隨便建的,反正沒用的邊遲早會被標記掉,關鍵路徑永遠不會被標記掉,而且肯定在所造的樹上(否則不用這條邊就沒辦法連通了,反證法,想想很簡單的)。然後是題目要刪邊,乾脆先把邊刪完,最後反著一條一條加回去,這樣就一樣了。
卡了很久,錯了三個地方:
1、LCA手賤把 step=dep[y]-dep[x] 寫成 step=dep[x]-dep[y] ,這樣step就是負數了,不知道哪裡來的60分(全部詢問輸出0就有60分,這資料……)
2、合併路徑時,x和y要先指向自己的父親節點,否則會重複更新。之前那道題沒有加,是因為那個更新不會造成影響,而這個不一樣。
3、陣列開小了,按照資料一直90,後來把陣列擴大5倍對了,然後仔細想發現山區的邊可能會重複問,這樣就要開兩倍,兩倍也是對的。(之前開了個1.5倍的,錯了)
Code(小長):
#include<bits/stdc++.h>
#define M 100005
using namespace std;
vector<int>edge[M];
set<int>edge1[M];
int n,m,len,cas;
int fa[20][M],fa2[M],ans[M],dep[M],op[M],Q[M];
struct node1{int x,y;}G[M*2],G1[M*2];
int Lt[M],Rt[M],dfsline[M],T;
void f(int x,int fa1){//隨機把圖變成一棵樹
Q[x]=1;
Lt[x]=++T;dfsline[T]=x;
fa[0][x]=fa1;dep[x]=dep[fa1]+1;
for(int i=0;i<(int)edge[x].size();i++){
int y=edge[x][i];
if(y==fa1)continue;
if(edge1[x].find(y)!=edge1[x].end())continue;//要刪除的邊不能放進去
if(Q[y]){G1[++len]=(node1){x,y};continue;}
f(y,x);
}
Rt[x]=T;
}
struct Tree{//線段樹部分
struct node{int L,R,sum;}tree[4*M];
void Build(int L,int R,int p){
tree[p].L=L;tree[p].R=R;tree[p].sum=0;
if(L==R){tree[p].sum=dep[dfsline[L]];return;}
int mid=(L+R)/2;
Build(L,mid,p*2);Build(mid+1,R,p*2+1);
}
int Query(int x,int p){
if(tree[p].L==tree[p].R)return tree[p].sum;
int mid=(tree[p].L+tree[p].R)/2;
if(mid>=x)return tree[p].sum+Query(x,p*2);
else return tree[p].sum+Query(x,p*2+1);
}
void Change(int L,int R,int p){
if(tree[p].L==L&&tree[p].R==R){
tree[p].sum--;return;
}
int mid=(tree[p].L+tree[p].R)/2;
if(mid>=R)Change(L,R,p*2);
else if(mid<L)Change(L,R,p*2+1);
else Change(L,mid,p*2),Change(mid+1,R,p*2+1);
}
}tree;
struct Lca{//LCA部分
void Init(){
for(int j=1;j<20;j++)
for(int i=1;i<=n;i++)
fa[j][i]=fa[j-1][fa[j-1][i]];
}
int LCA(int x,int y){
if(dep[x]>dep[y])swap(x,y);
int step=dep[y]-dep[x];//一開始這裡錯了
for(int i=0;i<20;i++)
if(step&(1<<i))y=fa[i][y];
if(x==y)return x;
for(int i=19;i>=0;i--)
if(fa[i][x]!=fa[i][y])
x=fa[i][x],y=fa[i][y];
return fa[0][x];
}
}LCA;
int Find(int x){return x==fa2[x]?x:fa2[x]=Find(fa2[x]);}
void Hebing(int x,int y){
x=Find(x),y=Find(y);//這兩句一開始沒加,然後執行錯,要是不加,下面的程式就會執行兩次,Tree.Change就會用兩次
while(x!=y){
if(dep[x]>=dep[y]){
tree.Change(Lt[x],Rt[x],1);
fa2[x]=fa[0][x];
x=Find(x);
}
else{
tree.Change(Lt[y],Rt[y],1);
fa2[y]=fa[0][y];
y=Find(y);
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&cas);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
for(int i=1;i<=cas;i++){
scanf("%d%d%d",&op[i],&G[i].x,&G[i].y);
if(op[i]==0)edge1[G[i].x].insert(G[i].y),edge1[G[i].y].insert(G[i].x);//把刪除的邊存下來
}
f(1,0);//造樹
LCA.Init();//LCA預處理
tree.Build(1,n,1);//建線段樹
for(int i=1;i<=n;i++)fa2[i]=i;
for(int i=1;i<=len;i++)Hebing(G1[i].x,G1[i].y);
for(int i=cas;i>=1;i--){
if(op[i]==0)Hebing(G[i].x,G[i].y);
else {//這裡更以前一樣
int lca=LCA.LCA(G[i].x,G[i].y);
int Lres=tree.Query(Lt[G[i].x],1),Rres=tree.Query(Lt[G[i].y],1),LCAres=tree.Query(Lt[lca],1);
ans[i]=Lres+Rres-LCAres*2;
}
}
for(int i=1;i<=cas;i++)
if(op[i])printf("%d\n",ans[i]);
return 0;
}
被第一題坑了,卡了一個多小時,哎……