2017 NOIp提高組 DAY2 試做
阿新 • • 發佈:2020-08-17
銜接 \(\text{DAY1}\)->\(2017\) \(\text{NOIp}\) 提高組 \(\text{DAY1}\) 試做
D2T1 乳酪
題庫原題,很裸的並查集,就是建邊相對複雜一些
D2T2 寶藏
題目描述
下方傳送門
題目連結
上方傳送門
思路分析
-
剛做的時候太著急了,上去就試著糊了個 \(\text{BFS}\),然後 \(\text{get}\) 到 \(40pts\)
-
後來看一眼資料範圍,\(n\) 才這麼小,直接暴搜,貪心和剪枝都不需要,直接列舉所有情況(全排列)就行,喜提 \(70pts\)
-
那要想 \(A\) 掉這道題,顯然如果還是直接暴搜是過不了 \(12\)
-
再看一眼資料範圍,正如林sir所說:
這資料範圍不狀壓嗎,瘋狂暗示
再如學長說的:
——你們不會有人列舉全排列直接暴搜吧?
——那用啥?
——狀壓呀。 -
所以直接用狀壓再優化一下就好了,小 \(DP\)
Code
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #define N 20 using namespace std; inline int read(){ int x = 0,f = 1; char ch = getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf = 0x3f3f3f3f; int n,m,e[N][N],dis[N],dp[1<<N],ans=inf; bool flag[N][N]; void dfs(int x){ for(int i = 1;i <= n;i++){ //列舉未訪問的點是由哪一個點轉移過來的即可 if(1<<(i-1)&x){ for(int j = 1;j <= n;j++){ if(!(1<<(j-1)&x)&&flag[i][j]){ if(dp[1<<(j-1)|x]>dp[x]+dis[i]*e[i][j]){// int pro = dis[j]; dis[j] = dis[i]+1; dp[1<<(j-1)|x]=dp[x]+dis[i]*e[i][j]; dfs(1<<(j-1)|x); dis[j] = pro; //搜完記得回溯 } } } } } } int main(){ memset(e,0x3f,sizeof(e)); n = read(),m = read(); for(int i = 1;i <= m;i++){ int u,v,w;u = read(),v = read(),w = read(); e[u][v] = e[v][u] = min(e[u][v],w); flag[u][v] = flag[v][u] = 1; } for(int i = 1;i <= 12;i++){ memset(dis,0x3f,sizeof(dis)); memset(dp,0x3f,sizeof(dp)); dis[i] = 1; dp[1<<(i-1)] = 0; dfs(1<<(i-1)); ans = min(ans,dp[(1<<n)-1]); } printf("%d\n",ans); return 0; }
D2T3
題目描述
下方傳送門
題目連結
上方傳送門
思路分析
暴力,30分,然後就不會了- 當然還是要有鑽研精神的,於是繼續
邊看題解邊想 - 對於行來說,每一行都有機會移動,然而對於列來說,移動的只有最後一列
- 每次移動,其實都相當於是一個刪除操作再加上一個插入操作,所以可以用平衡樹或者動態開點的線段樹來維護(樹狀陣列也彳亍,但我不會)
- 然後將行和列分開建樹,每一行建一個,列只建最後一列就行了,然後考慮各種操作
- 查詢和刪除:,這兩個是同步進行的,因為每次查詢都對應一次刪除。用線段樹記錄相應區間的刪除個數,此時區間內的點數就是區間大小減去刪除的。然後接下來考慮再刪的時候,和查 \(K\) 大相似如果要刪的數小於區間點數,就去左子樹刪,否則去右子樹刪
- 插入:開一個vector將所有插入的數裝下就好
- 查詢答案:
- 移動列,查詢這一列線段樹的第 \(x\) 個的位置 \(pos\),如果 \(pos<=n\) 說明是原來的移上去的,答案就是 \(pos*m\)。否則就是後來插進去的,輸出 \(vector\) 裡相應的元素即可
- 移動行,和上面的大同小異,行列顛倒即可
Code
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define N 300010
#define int long long
#define R register
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,q,sum[N*20],lch[N*20],rch[N*20],rt[N],mx,pos,cnt;
vector<int>vc[N];
void modify(int &u,int l,int r){ //更新刪除的點
if(!u)u = ++cnt;
sum[u]++;
if(l==r)return;
int mid = (l+r)>>1;
if(pos<=mid)modify(lch[u],l,mid);
else modify(rch[u],mid+1,r);
}
int query(int u,int l,int r,int w){ //查詢位置
if(l==r)return l;
int mid = (l+r)>>1;
int tmp = mid-l+1-sum[lch[u]];
if(w<=tmp)return query(lch[u],l,mid,w);
else return query(rch[u],mid+1,r,w-tmp);
}
int get_ans(int x,int y){ //移動列
pos = query(rt[n+1],1,mx,x);
modify(rt[n+1],1,mx);
int res = pos<=n ? pos*m : vc[n+1][pos-n-1];
vc[n+1].push_back(y?y:res);
return res;
}
int get_ans1(int x,int y){ //移動行
pos = query(rt[x],1,mx,y);
modify(rt[x],1,mx);
int res = pos<m? (x-1)*m+pos : vc[x][pos-m];
vc[x].push_back(get_ans(x,res)); //再移一下列
return res;
}
signed main(){
n = read(),m = read(),q = read();
mx = max(n,m)+q;
for(int i =1;i <= q;i++){
int x,y;x = read(),y = read();
printf("%lld\n",y==m ? get_ans(x,0) : get_ans1(x,y));
}
return 0;
}
總結
- \(T1\) 並查集還算是比較水的
- \(T2\) 暴力分還是很足,當然正解也就是優化一下,並無太大本質區別
- \(T3\) 就有點ex了,30分有手就行,正解雖然線段樹的程式碼很短但是不太好想,畢竟壓軸題
- 再結合一下 \(D1\) 的,不翻車的話 \(300\) 分以上應該沒啥問題, \(T1\) 都 \(A\) 掉的話在適當拿一些暴力即可,發揮好可以到 \(400\) 分左右,不過可能存在誤差,畢竟環境不太一樣
附洛谷對應題單->2017 NOIp