题意:有一本书有 n n n 卷,你需要从第一卷开始依次看,一旦没有某一卷就停止。在看之前 你可以进行若干次操作,每次卖掉任意两卷并买新的任意一卷。问操作结束后最多能看多少卷。
做法 1
注意到拥有的重复的卷都可以没有损失地卖掉,提前记录一下。然后从小到大扫,如果没有这一卷就尝试卖两本并买这一本。卖的两本优先从重复的卷里考虑,然后再贪心从大到小卖。正确性显然,数据结构维护即可。
By KrK
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <bits/stdc++.h> using namespace std;int n;set <int > S; int cur;int main () { scanf ("%d" , &n); for (int i = 0 ; i < n; i++) { int a; scanf ("%d" , &a); if (S.count (a)) cur++; else S.insert (a); } for (int i = 1 ; ; i++) if (!S.count (i)) { while (cur < 2 && !S.empty () && *S.rbegin () > i) { auto it = S.end (); it--; cur++; S.erase (it); } if (cur >= 2 ) { cur -= 2 ; S.insert (i); } else { printf ("%d\n" , i - 1 ); return 0 ; } } return 0 ; }
做法 2
考虑二分答案。对于读 i i i 卷的情况,将 > i + 1 >i+1 > i + 1 的卷和 ≤ i \le i ≤ i 的重复的卷全部卖掉替换,判断是否能填补空隙。
By heno239
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void solve () { int n; cin >> n; vector<int > a (n) ; rep (i, n)cin >> a[i]; auto can = [&](int x) { vector<int > cnt (x+1 ); int z = 0 ; rep (i, n) { if (a[i] > x)z++; else { if (cnt[a[i]])z++; cnt[a[i]] = 1 ; } } int r = 0 ; rep1 (i, x) { if (!cnt[i])r++; } if (z >= 2 * r)return true ; return false ; }; int le = 0 , ri = n + 5 ; while (ri - le > 1 ) { int m = (le + ri) / 2 ; if (can (m)) { le = m; } else { ri = m; } } cout << le << "\n" ; }
做法 3
与二分答案类似,但将二分改成了枚举。注意到读的卷数一定不会超过 n n n ,所以 > n >n > n 的卷都可以直接卖掉。考虑统计能否读 i i i 卷。考虑设计 b a k [ i ] bak[i] b a k [ i ] 表示 ≤ i \le i ≤ i 的卷有多少种 ,则如果要读 i i i 卷,将会有 i − b a k [ i ] i-bak[i] i − b a k [ i ] 个位置空缺。这些位置可以使用多余的补上,而只有 b a k [ i ] bak[i] b a k [ i ] 卷是不多余的(每种只有一卷不多余,剩下的都多余),所以总的多余的数量为 n − b a k [ i ] n-bak[i] n − b a k [ i ] ,比较空缺位置与多余数量的一半即可。
By He_Ren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std;typedef long long ll;typedef pair<int ,int > pii;const int MAXN = 3e5 + 5 ;int a[MAXN], bak[MAXN];int main (void ) { int n; scanf ("%d" ,&n); for (int i=1 ; i<=n; ++i) scanf ("%d" ,&a[i]); for (int i=1 ; i<=n; ++i) if (a[i] <= n) bak[a[i]] = 1 ; for (int i=1 ; i<=n+1 ; ++i) bak[i] += bak[i-1 ]; for (int i=1 ; i<=n+1 ; ++i) { if (i - bak[i] > (n - bak[i]) / 2 ) { printf ("%d\n" ,i-1 ); return 0 ; } } return 0 ; }
错解
By daisybunny
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;int n;int a[300005 ];map<int ,int > mp; int ba;int ans;int main () { cin>>n; for (int t=0 ;t<n;t++) { cin>>a[t]; mp[a[t]]++; } sort (a,a+n); for (int t=1 ;;t++) { if (mp[t]==0 ) { if (ba>1 ) { ba-=2 ; ans++; } else if (n>=1 &&t<a[n-1 ]&&ba==1 ) { mp[a[n-1 ]]--;n--;ba--;ans++; } else if (n>=2 &&t<a[n-2 ]) { mp[a[n-1 ]]--;mp[a[n-2 ]]--;n-=2 ;ans++; } else break ; } else { ans++; ba+=mp[t]-1 ; mp[t]--; } } cout<<ans; return 0 ; }
一组 hack 数据为
此时在算到 2 的空缺时,该代码会优先卖掉两个 5,而最优解应该卖掉多余的一个 4 一个 5。此程序的问题在于,他考虑到了优先卖掉重复的,但只判断了优先卖掉 ≤ i \le i ≤ i 的重复的,而没有优先处理 > i >i > i 的重复。
题意:有 n n n 张卡片,第 i i i 张正面为 a i a_i a i ,反面为 b i b_i b i ,问是否存在一种翻转方案使得卡片的和为 S S S 。如果有,输出方案。
设 d p [ i ] [ j ] = 0 / 1 dp[i][j]=0/1 d p [ i ] [ j ] = 0 / 1 表示前 i i i 张卡片能否得到 j j j ,则
d p [ i ] [ j ] = d p [ i − 1 ] [ j − a i ] or d p [ i − 1 ] [ j − b i ] dp[i][j]=dp[i-1][j-a_i] \text{ or } dp[i-1][j-b_i]
d p [ i ] [ j ] = d p [ i − 1 ] [ j − a i ] or d p [ i − 1 ] [ j − b i ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <bits/stdc++.h> using namespace std;int a[110 ],b[110 ];bool f[110 ][10010 ],pre[110 ][10010 ];void get (int now,int x) { if (now==0 ) return ; if (pre[now][x]) get (now-1 ,x-a[now]); else get (now-1 ,x-b[now]); cout<<(pre[now][x]?'H' :'T' ); } signed main () { int n,s; cin>>n>>s; for (int i=1 ;i<=n;i++) cin>>a[i]>>b[i]; f[0 ][0 ]=1 ; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=s;j++) { if (j>=a[i]&&f[i-1 ][j-a[i]]) f[i][j]=1 ,pre[i][j]=1 ; if (j>=b[i]&&f[i-1 ][j-b[i]]) f[i][j]=1 ,pre[i][j]=0 ; } if (f[n][s]) { cout<<"Yes" <<endl; get (n,s); cout<<endl; } else cout<<"No" <<endl; return 0 ; }
题意:有 n n n 个点 m m m 条有向有权边 u i → w i v i u_i\xrightarrow{w_i} v_i u i w i v i 和一个边的编号序列 e i e_i e i ,你可以选择编号序列的一个子序列,使得该子序列的边构成一条 1 → n 1\to n 1 → n 的路径,求最短路径。
直接 DP。令 f i , j f_{i,j} f i , j 表示只考虑序列中前 j j j 条边,1 → j 1\to j 1 → j 的最短路径,则
f i , j = { min ( f i − 1 , j , f i − 1 , u e i + w e i ) if v e i = j f i − 1 , j otherwise f_{i,j}=\begin{cases}\min(f_{i-1,j},f_{i-1,u_{e_i}}+w_{e_i})\text{ if }v_{e_i}=j\\f_{i-1,j}\text{ otherwise}\end{cases}
f i , j = { min ( f i − 1 , j , f i − 1 , u e i + w e i ) if v e i = j f i − 1 , j otherwise
此时第一维可以省略。转移时只需要更新 f v a i f_{v_{a_i}} f v a i 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <bits/stdc++.h> using namespace std;#define int long long struct edge { int u,v,w; } a[200010 ]; int e[200010 ];int f[200010 ];signed main () { int n,m,k; cin>>n>>m>>k; for (int i=1 ;i<=m;i++) cin>>a[i].u>>a[i].v>>a[i].w; for (int i=1 ;i<=k;i++) cin>>e[i]; memset (f,-1 ,sizeof (f)); f[1 ]=0 ; for (int i=1 ;i<=k;i++) { if (f[a[e[i]].u]==-1 ) continue ; if (f[a[e[i]].v]==-1 ||f[a[e[i]].v]>f[a[e[i]].u]+a[e[i]].w) f[a[e[i]].v]=f[a[e[i]].u]+a[e[i]].w; } cout<<f[n]<<endl; return 0 ; }
题意:有一个 n × n n\times n n × n 的矩阵,每个格子有一个正整数,每次只能向右走或向下走,问有多少条路径能从 ( 1 , 1 ) (1,1) ( 1 , 1 ) 走到 ( n , n ) (n,n) ( n , n ) ,且路径异或和为 0 0 0 。
双向搜索。搜索从 ( 1 , 1 ) (1,1) ( 1 , 1 ) 到副对角线的所有方案,存到 m a p map m a p 里,再搜索从 ( n , n ) (n,n) ( n , n ) 到副对角线的所有方案,计算答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <bits/stdc++.h> using namespace std;int n,a[30 ][30 ];map<int ,int > m[2 ][21 ]; void dfs (int x,int y,int f,int flag) { f^=a[x][y]; if (x+y==n+1 ) m[flag][x][f]++; else if (flag) dfs (x-1 ,y,f,flag),dfs (x,y-1 ,f,flag); else dfs (x+1 ,y,f,flag),dfs (x,y+1 ,f,flag); } signed main () { cin>>n; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=n;j++) cin>>a[i][j]; dfs (1 ,1 ,0 ,0 ); dfs (n,n,0 ,1 ); long long ans=0 ; for (int i=1 ;i<=n;i++) { int x=a[i][n+1 -i]; for (auto tmp:m[0 ][i]) ans+=1ll *tmp.second*m[1 ][i][tmp.first^x]; } cout<<ans<<endl; return 0 ; }