比赛传送门
B. Playing Cards Validation
题意:有 n n n 个长度为 2 2 2 的字符串,判断是否满足以下条件:
第一个字符为 HDCS
之一。
第二个字符为 A23456789TJQK
之一。
字符串两两不同。
一个模拟题。可以将两个字符可能的选择分别记录下来,循环一遍判断是否为其中之一或调用 count 函数;也可以直接写 if 判断。判断两两不同可以 n 2 n^2 n 2 枚举,也可以用 set 或排序加 unique 去重。
By cxm1024
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> using namespace std;string a="HDCS" ,b="A23456789TJQK" ; void NO () {puts ("No" );exit (0 );}set<string> st; signed main () { int n; cin>>n; for (int i=1 ;i<=n;i++) { string s; cin>>s; bool flag=0 ; for (char c:a) if (s[0 ]==c) flag=1 ; if (!flag) NO (); flag=0 ; for (char c:b) if (s[1 ]==c) flag=1 ; if (!flag) NO (); st.insert (s); } if (st.size ()!=n) NO (); puts ("Yes" ); return 0 ; }
By yuiop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> int main () { int n,h=1 ,i,j,u[550 ]={}; char s[]={"HDCS" },t[]={"A23456789TJQK" }; scanf ("%d" ,&n); while (n--){ scanf (" %c%c" ,&s[4 ],&t[13 ]); for (i=0 ;s[i]!=s[4 ];i++); for (j=0 ;t[j]!=t[13 ];j++); if (i==4 ||j==13 )h=0 ; if (u[i*100 +j])h=0 ; u[i*100 +j]=1 ; } printf ("%s\n" ,h?"Yes" :"No" ); return 0 ; }
这份代码是使用一个指针,一直扫到匹配。然而将待判断字符串与合法字符集混在一起的写法容易产生混淆,并不利于代码可读性和调试难度。写代码时每个部分的用途要尽量明确,不要有一个结构存多个意义的情况。
修改后可以为:
fixed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> using namespace std;void NO () {puts ("No" );exit (0 );}string a="HDCS" ,b="A23456789TJQK" ; set<string> st; signed main () { int n; cin>>n; for (int i=1 ;i<=n;i++) { string s; cin>>s; int x=0 ,y=0 ; while (s[0 ]!=a[x]&&x<a.size ()) x++; while (s[1 ]!=b[y]&&y<b.size ()) y++; if (x==a.size ()||y==b.size ()) NO (); st.insert (s); } if (st.size ()!=n) NO (); puts ("Yes" ); return 0 ; }
By hitonanode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void bad () { puts ("No" ); exit (0 ); } int main () { int N; cin >> N; set<string> se; REP (_, N) { string s; cin >> s; const string suit = "HDCS" ; const string num = "A23456789TJQK" ; if (!count (ALL (suit), s.front ())) bad (); if (!count (ALL (num), s.back ())) bad (); se.insert (s); } if (se.size () < N) bad (); puts ("Yes" ); }
这份代码使用 count 函数进一步简化过程。
C. Ladder Takahashi
题意:给你一张 n n n 个点 m m m 条边的无向图,问点 1 1 1 能到达的点中编号最大的。n ≤ 1 0 9 , m ≤ 2 × 1 0 5 n\le 10^9,m\le 2\times 10^5 n ≤ 1 0 9 , m ≤ 2 × 1 0 5 。
很简单的判断连通性问题,可以 DFS 也可以 BFS。主要需要处理的问题在于,点的编号可以很大,常规存图方式不可行。有以下两种方法处理:
离散化
By SSRS
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 47 48 49 #include <bits/stdc++.h> using namespace std;int main () { int N; cin >> N; vector<int > A (N) , B (N) ; for (int i = 0 ; i < N; i++){ cin >> A[i] >> B[i]; } vector<int > X; X.push_back (1 ); for (int i = 0 ; i < N; i++){ X.push_back (A[i]); X.push_back (B[i]); } sort (X.begin (), X.end ()); X.erase (unique (X.begin (), X.end ()), X.end ()); int M = X.size (); for (int i = 0 ; i < N; i++){ A[i] = lower_bound (X.begin (), X.end (), A[i]) - X.begin (); B[i] = lower_bound (X.begin (), X.end (), B[i]) - X.begin (); } vector<vector<int >> E (M); for (int i = 0 ; i < N; i++){ E[A[i]].push_back (B[i]); E[B[i]].push_back (A[i]); } vector<bool > used (M, false ) ; used[0 ] = true ; queue<int > Q; Q.push (0 ); while (!Q.empty ()){ int v = Q.front (); Q.pop (); for (int w : E[v]){ if (!used[w]){ used[w] = true ; Q.push (w); } } } int ans = 0 ; for (int i = 0 ; i < M; i++){ if (used[i]){ ans = max (ans, X[i]); } } cout << ans << endl; }
使用 map 处理
By cxm1024
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std;map<int ,vector<int > > a; map<int ,bool > vis; int ans=0 ;void dfs (int now) { if (vis[now]) return ; vis[now]=1 ,ans=max (ans,now); auto &to=a[now]; for (int v:to) dfs (v); } signed main () { int n; cin>>n; for (int i=1 ;i<=n;i++) { int x,y; cin>>x>>y; a[x].push_back (y); a[y].push_back (x); } dfs (1 ); cout<<ans<<endl; return 0 ; }
D. Takahashi’s Solitaire
题意:有 n n n 张卡片,卡片上数字的值域为 [ 0 , m ) [0,m) [ 0 , m ) ,你需要选择若干张卡片放到桌子上,要求放的卡片的数字等于上一次放的数字,或等于上一次放的数字 + 1 +1 + 1 后 m o d m \bmod m m o d m 。最小化剩余的卡片的数字和。
转化为最大化放的卡片的数字和。首先将卡片排序。一个显然的结论是,每次选一个数字时必然要全部选完。实现上有以下几种处理方法:
断环为链
由于值域上 m m m 和 0 0 0 连续,可能形成环,所以将排好序的数组复制两遍,断环为链。循环时维护当前尽可能长的连续段和即可。连续段的数字和计算可以使用前缀和加速。无需特判长度是否超过 n n n ,因为此时数字和也会超过总数字和,只需要将答案和 0 0 0 取 max \max max 即可。
By cxm1024
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std;#define int long long int a[400010 ];signed main () { int n,m,sum=0 ; cin>>n>>m; for (int i=1 ;i<=n;i++) cin>>a[i]; sort (a+1 ,a+n+1 ); for (int i=n+1 ;i<=n+n;i++) a[i]=a[i-n],sum+=a[i]; int now=a[1 ],ans=0 ; for (int i=2 ;i<=n+n;i++) { if (a[i-1 ]!=a[i]&&(a[i-1 ]+1 )%m!=a[i]) { ans=max (ans,now); now=a[i]; } else now+=a[i]; } ans=max (ans,now); cout<<max (0ll ,sum-ans)<<endl; return 0 ; }
环上记录断点
直接在环上处理,记录每一个不连续的位置,扫一遍统计答案。相邻两个断点之间即为连续段。
By SSRS
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 #include <bits/stdc++.h> using namespace std;int main () { int N, M; cin >> N >> M; vector<int > A (N) ; for (int i = 0 ; i < N; i++){ cin >> A[i]; } sort (A.begin (), A.end ()); long long sum = 0 ; for (int i = 0 ; i < N; i++){ sum += A[i]; } vector<int > p; for (int i = 0 ; i < N; i++){ int j = (i + N - 1 ) % N; if (A[j] != (A[i] + M - 1 ) % M && A[j] != A[i]){ p.push_back (i); } } if (p.empty ()){ cout << 0 << endl; } else { int cnt = p.size (); long long mx = 0 ; for (int i = 0 ; i < cnt; i++){ int r = p[(i + 1 ) % cnt]; if (i == cnt - 1 ){ r += N; } long long tmp = 0 ; for (int j = p[i]; j < r; j++){ tmp += A[j % N]; } mx = max (mx, tmp); } cout << sum - mx << endl; } }
循环移位
将 j → m → 0 → i j\to m\to 0\to i j → m → 0 → i 的一整个连续段放到最后,从 i + 1 i+1 i + 1 开始处理到 i i i 为止。
By kotatsugame
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 #include <iostream> #include <vector> #include <algorithm> using namespace std;int N,M;int A[2 <<17 ];main (){ cin>>N>>M; for (int i=0 ;i<N;i++)cin>>A[i]; sort (A,A+N); int id; if ((A[N-1 ]+1 )%M!=A[0 ])id=0 ; else { id=1 ; while (id<N&&A[id-1 ]+1 >=A[id])id++; if (id==N) { cout<<0 <<endl; return 0 ; } } long ans=0 ; for (int i=0 ;i<N;) { int j=i+1 ; long now=A[(id+i)%N]; while (j<N) { int y=A[(id+j)%N]; int x=A[(id+j-1 )%N]; if (y==x||y==(x+1 )%M) { now+=y; j++; } else break ; } i=j; ans=max (ans,now); } for (int i=0 ;i<N;i++)ans-=A[i]; cout<<-ans<<endl; }
并查集
用并查集维护连通的值域,维护每个块的数字和,最后找最大的块即可。
By kzyKT
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 ll p[200001 ],r[200001 ],c[200001 ]; void init (ll n) {rep (i,n)p[i]=i,r[i]=0 ,c[i]=0 ;}int find (int x) {return (p[x]==x)?x:(p[x]=find (p[x]));}void unite (int x,int y) { x=find (x),y=find (y); if (x==y)return ; ll cx=c[x],cy=c[y]; if (r[x]<r[y])p[x]=y; else {p[y]=x;if (r[x]==r[y])r[x]++;} c[find (x)]=cx+cy; } bool same (int x,int y) {return find (x)==find (y);}void Main () { ll n,m; cin >> n >> m; ll a[n]; rep (i,n) R a[i]; init (n); map<ll,vector<ll>> ma; rep (i,n) { ma[a[i]].pb (i); } ll sum=0 ; rep (i,n) { c[i]=a[i]; sum+=a[i]; } tr (it,ma) { REP (i,1 ,it->S.size ()) unite (it->S[0 ],it->S[i]); if (ma.count ((it->F+1 )%m)) unite (it->S[0 ],ma[(it->F+1 )%m][0 ]); } ll ans=sum; rep (i,n) { if (find (i)==i) ans=min (ans,sum-c[i]); } pr (ans); } int main () {ios::sync_with_stdio (0 );cin.tie (0 );Main ();return 0 ;}
遍历 map
用 map 存下每个数字及出现次数,在 map 上遍历,遇到断点就扫到下一个断点,没有断点特判以下即可。
By waltz
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 47 48 49 50 51 52 53 54 55 using namespace std;map<int , long long > ans; int main () { #ifdef absi2011 freopen ("input.txt" ,"r" ,stdin); freopen ("output.txt" ,"w" ,stdout); #endif int n,m; scanf ("%d%d" ,&n,&m); int i; long long sum_ans = 0 ; for (i=0 ;i<n;i++) { int x; scanf ("%d" ,&x); ans[x % m] += x; sum_ans += x; } long long max_dec = 0 ; map<int , long long >::iterator ii; for (ii = ans.begin (); ii != ans.end (); ii++) { int x = (*ii).first; if (ans.find ((x-1 +m)%m) == ans.end ()) { long long dec = (*ii).second; map<int , long long >::iterator jj = ii; for (;;) { jj ++; if (jj == ans.end ()) { jj = ans.begin (); } if ((*jj).first == ((x + 1 ) % m)) { dec += (*jj).second; x = (*jj).first; } else { break ; } } max_dec = max (max_dec, dec); } } if (max_dec == 0 ) { max_dec = sum_ans; } printf ("%lld\n" , sum_ans - max_dec); return 0 ; }
E. Crystal Switches
题意:有一张图,边有 01 两种颜色,只能走颜色 1 的边。有 k k k 个特殊点,当你到达特殊点时,可以选择将图中每条边的颜色取反(也可以不取反)。问从 1 1 1 号点走到 n n n 号点的最短路(取反不算一步)。
将图中每条边的颜色取反,可以看成将当前能走的颜色取反,于是在普通最短路的基础上修改一下即可。记录状态的时候不仅要记录当前的点和距离,还要记录当前能走的颜色。转移时,若当前点不为特殊点,则只能通过可走的边转移到下一个点,否则能通过所有边转移到下一个点。用 Dijkstra 和 BFS 均可。
Dijkstra
Dijkstra 的做法比较显然但较难写:
By cxm1024
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;vector<pair<int ,bool > > a[200010 ]; bool swt[200010 ];struct node { int x,val,flag; bool operator <(const node &o)const { return val>o.val; } }; priority_queue<node> q; bool done[200010 ][2 ];int ans[200010 ][2 ];void dijkstra () { q.push ({1 ,0 ,0 }); while (!q.empty ()) { auto [u,w,flag]=q.top ();q.pop (); if (done[u][flag]) continue ; done[u][flag]=1 ,ans[u][flag]=w; for (auto v:a[u]) { if (done[v.first][1 -v.second]) continue ; if (v.second^flag) q.push ({v.first,w+1 ,flag}); else if (swt[u]) q.push ({v.first,w+1 ,1 -flag}); } } } signed main () { int n,m,k; cin>>n>>m>>k; for (int i=1 ;i<=m;i++) { int u,v,x; cin>>u>>v>>x; a[u].push_back ({v,x}); a[v].push_back ({u,x}); } for (int i=1 ;i<=k;i++) { int x; cin>>x; swt[x]=1 ; } dijkstra (); if (!done[n][0 ]&&!done[n][1 ]) cout<<-1 <<endl; else if (done[n][0 ]&&done[n][1 ]) cout<<min (ans[n][0 ],ans[n][1 ])<<endl; else cout<<ans[n][0 ]*done[n][0 ]+ans[n][1 ]*done[n][1 ]<<endl; return 0 ; }
01BFS
用 01BFS 来处理最短路。对于特殊点,我们可以看做该点向自己连一条权值为 0 0 0 的边,作用为翻转当前能走的颜色。在实现中即为 push_front
“翻转颜色后的自己”到队列首。
By kotatsugame
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 47 48 49 #include <iostream> #include <vector> #include <deque> using namespace std;int N,M,K;bool S[2 <<17 ];int D[2 <<17 ][2 ];vector<int >G[2 <<17 ][2 ]; main (){ cin>>N>>M>>K; for (int i=0 ;i<M;i++) { int u,v,a;cin>>u>>v>>a; u--,v--; G[u][a].push_back (v); G[v][a].push_back (u); } for (int i=0 ;i<K;i++) { int s;cin>>s; S[s-1 ]=true ; } for (int i=0 ;i<N;i++)D[i][0 ]=D[i][1 ]=1e9 ; D[0 ][1 ]=0 ; deque<pair<int ,int > >Q; Q.push_back (make_pair (0 ,1 )); while (!Q.empty ()) { int u=Q.front ().first,f=Q.front ().second; Q.pop_front (); if (S[u]) { if (D[u][!f]>D[u][f]) { D[u][!f]=D[u][f]; Q.push_front (make_pair (u,!f)); } } for (int v:G[u][f])if (D[v][f]>D[u][f]+1 ) { D[v][f]=D[u][f]+1 ; Q.push_back (make_pair (v,f)); } } int ans=min (D[N-1 ][0 ],D[N-1 ][1 ]); if (ans==(int )1e9 )ans=-1 ; cout<<ans<<endl; }
Dijkstra+虚点
我们对于每个点 i i i 建立一个对应点 i + n i+n i + n ,为翻转当前颜色后的点。对于颜色为 1 的边,只能在初始颜色的情况下走,所以连接两个点 i , j i,j i , j 。对于颜色为 0 的边,只能在翻转颜色后的图上走,所以连接 i + n , j + n i+n,j+n i + n , j + n 。于是我们得到了两个完全独立的图。而特殊点的作用就是连接这两个图。对于每个特殊点 i i i ,连接 i , i + n i,i+n i , i + n ,权值为 0 即可。
By hitonanode
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 int main () { int N, M, K; cin >> N >> M >> K; shortest_path<int > graph (N * 2 ) ; REP (e, M) { int u, v, a; cin >> u >> v >> a; --u, --v; if (a) { graph.add_bi_edge (u, v, 1 ); } else { graph.add_bi_edge (u + N, v + N, 1 ); } } while (K--) { int s; cin >> s; --s; graph.add_bi_edge (s, N + s, 0 ); } graph.solve (0 ); int ret = min (graph.dist[N - 1 ], graph.dist[N * 2 - 1 ]); if (ret > 1 << 25 ) ret = -1 ; cout << ret << endl; }