第二届“清华社杯”大学生算法大赛个人题解
A题 变化的矩形
【题意】有一个面积为 S (1 < S < 1e9) 的矩形。在我们对其进行以下操作后,您必须算出它的新面积大小:
1、长度增加 50%
2、宽度减少 50%
#include <bits/stdc++.h> using namespace std; int main(){ int n; cin >>n; long double ans = 0.75*n; cout << fixed << setprecision(6) << ans; return 0; }
B题 吃鸡梦之队
【题意】有来自 4 台服务器的 N (4 ≤ N ≤ 1e5) 名吃鸡玩家,每名玩家只属于一台服务器。你知道每个玩家的技能值。现在上级命令你从每台服务器挑选一名玩家组建吃鸡梦之队。该队须满足以下两个条件:
设来自 1 号服务器的玩家的技能值为 X1 ,来自 2 号服务器的玩家的技能值为 X2 ,来自 3 号服务器的玩家的技能值为 X3 ,来自 4 号服务器的玩家的技能值为 X4 ,
组队条件是:
组建一只吃鸡梦之队,满足上述条件并且让 d (0 ≤ d) 的值尽可能小。
【思路】显然,必须要让 X1 和 X2 、X3 和 X4 尽可能接近是最优的。
我们先对这四类数进行排序。再不妨设X1<X2,通过枚举X1二分找到X2。再通过X1 - d ≤ X3 ≤X2 + d确定X3,X4即可。
#include <bits/stdc++.h> #define endl '\n' #define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) using namespace std; int a[100005]; inline void sol(){ vector<int> v[5]; int n; cin >> n; for(int i=1;i<=n;i++) cin >> a[i]; for(int i=1;i<=n;i++){int x; cin>>x; v[a[i]].push_back(x);} for(int i=1;i<=4;i++) sort(v[i].begin(),v[i].end()); int l=0, r=INT_MAX, ans=0; while(l<=r){ int d = (l+r)>>1; bool flag=0; for(auto &x1:v[1]){ auto pos = lower_bound(v[2].begin(),v[2].end(),x1); if(pos==v[2].end()) break; int x2 = *pos, delta = x2 - d; pos = lower_bound(v[3].begin(),v[3].end(),delta); if(pos==v[3].end()) continue; int x3 = *pos; pos = lower_bound(v[4].begin(),v[4].end(),delta); if(pos==v[4].end()) continue; int x4 = *pos; if(max(x4,x3)<=x1+d){flag=1; break;} } for(auto &x2:v[2]){ auto pos = lower_bound(v[1].begin(),v[1].end(),x2); if(pos==v[1].end()) break; int x1 = *pos, delta = x1 - d; pos = lower_bound(v[3].begin(),v[3].end(),delta); if(pos==v[3].end()) continue; int x3 = *pos; pos = lower_bound(v[4].begin(),v[4].end(),delta); if(pos==v[4].end()) continue; int x4 = *pos; if(max(x4,x3)<=x2+d){flag=1; break;} } flag ? ans=d, r=d-1 : l=d+1; } cout << ans << endl; } int main(){ IO; int ttt; cin >> ttt; while(ttt--) sol(); return 0; }
C题 洞穴探险
【题意】N个点, M 条通路,Q 次询问,第 i 个问题是点 Xi 和点 Yi 之间有没有路径,必须告诉回答没有路径(NONE)、一条路径(ONE)或存在多条路径(MORE THAN ONE)。注意,路径不应多次通过同一个小洞,也不应多次通过同一条隧道。
【思路】以下代码应该是WA + TLE了,回头看看能不能补题,要解决这个数据量的无向图连接问题,应该是要用上并查集(?)
#include <bits/stdc++.h> #define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) #define ll long long #define endl '\n' using namespace std; class Graph { public: Graph(int n) : N(n), adj(n) {} void addEdge(int x, int y) { adj[x].push_back(y); adj[y].push_back(x); } // 深度优先搜索,用于查找从x到y是否有路径 bool dfs(int start, int target, vector<bool>& visited) { if (start == target) return true; visited[start] = true; for (int node : adj[start]) { if (!visited[node]) { if (dfs(node, target, visited)) return true; } } return false; } string ans(int Xi, int Yi) { vector<bool> visited(N, false); // 初始调用DFS bool hasPath = dfs(Xi, Yi, visited); if(!hasPath) return "NONE"; else { fill(visited.begin(), visited.end(), false); adj[Yi].erase(remove(adj[Yi].begin(), adj[Yi].end(), Xi), adj[Yi].end()); adj[Xi].erase(remove(adj[Xi].begin(), adj[Xi].end(), Yi), adj[Xi].end()); if(dfs(Xi, Yi, visited)) return "MORE THAN ONE"; else return "ONE"; } } private: int N; vector<vector<int>> adj; }; int main() { IO; int n, m, q; cin >> n >> m >> q; Graph g(n); for(int i=0;i<m;i++) { int x, y; cin >> x >> y; g.addEdge(x,y); } for(int i=0;i<q;i++) { int x, y; cin >> x >> y; cout << g.ans(x,y) << endl; } return 0; }
D题 火柴棒等式
【题意】这是一个由火柴棒组成的等式(如: 9 - 1 = 4),但它可能不正确。我们需要找到使等式正确所需的最少移动次数(如果没有的话输出-1)。
【思路】枚举,等式由“数字-符号-数字-等于号-数字”组成,所以总情况数为10*2*10*10=2000种。在考虑每个数字最多由7根火柴棒组成,考虑用二进制01串来表述
#include <bits/stdc++.h> #define ll long long using namespace std; map<ll, bool> mp; // <<0表示上边 <<1表示右上边 <<2表示右下边 <<3表示下边 // <<4表示左下边 <<5表示左上边 <<6表示中边 const int num[10] = {//分别为0~9 (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 4)+(1 << 5), (1 << 1)+(1 << 2), (1 << 0)+(1 << 1)+(1 << 3)+(1 << 4)+(1 << 6), (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 6), (1 << 1)+(1 << 2)+(1 << 5)+(1 << 6), (1 << 0)+(1 << 2)+(1 << 3)+(1 << 5)+(1 << 6), (1 << 0)+(1 << 6)+(1 << 2)+(1 << 3)+ (1 << 4)+(1 << 5), (1 << 0)+(1 << 1)+(1 << 2), (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 4)+(1 << 5)+(1 << 6), (1 << 0)+(1 << 1)+(1 << 2)+(1 << 3)+(1 << 5)+(1 << 6) }; ll trans(int a, int op, int b, int c){ ll encoder = 0; encoder += num[a]; encoder <<= 7; encoder += num[b]; encoder <<= 7; encoder += num[c]; encoder <<= 2; if(op==2) return encoder+=3;//(11)2 else return encoder+=2; //(10)2 } void init(){ for(int x1=0;x1<10;x1++) for(int x2=0;x2<10;x2++) for(int x3=0;x3<10;x3++) for(int op=1;op<=2;op++) if((op==1 && x1-x2==x3)||(op==2 && x1+x2==x3)) mp[trans(x1,op,x2,x3)]=1; } inline void sol(){ int a, b, c, op, ans = 999999; char ch, useles; cin >> a >> ch >> b >> useles >> c; op = ch=='+'?2:1; ll now = trans(a,op,b,c), cnt1 = __builtin_popcount(now); for(auto &x:mp){ ll tmp = x.first; if(__builtin_popcount(tmp)!=cnt1) continue; int cnt = 0; for(int i=0;i<30;i++){ if(((now>>i)&1)!=((tmp>>i)&1)) ++cnt; ans=min(ans,cnt/2); } if(ans==999999) puts("-1"); else cout << ans << endl; } int main(){ int ttt; cin >> ttt; init(); while (ttt--) sol(); return 0; }
E题 排序
【题意】构造一个排列,使得每两个连续数字之间的绝对差的总和 f(p) 最大。如n=5时,排列 4 1 5 2 3 的可取到 f(p) 最大值11。
【思路】贪心,最大最小的数先放一起,让后用双端数组看看是放左边收益大还是右边收益大,最后求和 f(p) 即可。
#include <bits/stdc++.h> #define ll long long #define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) using namespace std; const int N =1e5+5; int a[N],q[N]; void sol(){ int n; ll sum=0; deque<int> a; cin >> n; a.push_back(1),a.push_back(n); sum+=abs(n-1); int p1=1,p2=n,cnt=0; while(p1!=p2){ ++cnt;int i; if(cnt&1) i=(++p1); else i=(--p2); if(p1==p2) break; if(abs(i-a.back())>abs(i-a.front())) sum+=abs(i-a.back()),a.push_back(i); else sum+=abs(i-a.front()),a.push_front(i); } cout <<sum << "\n"; } int main(){ IO; int ttt; cin >>ttt; while(ttt--) sol(); return 0; }
F题 数组查询
【题意】给定一个数组,第i次查询时给一个数Xi,并询问数组前i个元素中小于等于Xi的最大元素。
【思路】采用multiset模拟,STL中set容器的upper_bound(k)函数可以返回一个指向键k之后下一个元素的迭代器。
#include <bits/stdc++.h> #define IO ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) #define ll long long const int N = 1e5+5; using namespace std; ll a[N]; inline void sol(){ int n; cin>>n; for(int i=1;i<=n;i++) cin >> a[i]; multiset<int> s; for(int i=1;i<=n;i++){ int x; cin >> x; s.insert(a[i]); auto it = s.upper_bound(x); if(it==s.begin()) puts("-1"); else cout << *(--it) << endl; } } int main(){ IO; int ttt; cin >> ttt; while(ttt--) sol(); return 0; }
G题 新冠病毒
【题意】已知第i天新增i个病例,计算n天内的感染总数和平均感染数。
#include <bits/stdc++.h> #define ll long long int main(){ ll n; std::cin >> n; std::cout << n*(n+1)/2 << '\n' << (n+1)/2; return 0; }
H题 有星星就表白
【题意】每次给定一个由 N 个顶点和 M 条边组成的连通无向图(不包含自环或重边),如果是菊花图则输出Yes,否则输出No。
【思路】显然只能有n-1条边,图要联通。然后一个点作为中心,因此只有一个点的度数可以大于2。就做完了。
#include <bits/stdc++.h> #define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) #define ll long long using namespace std; const int N =1e5+5; ll a[N],f[N]; int find(int x){ return f[x]==x ? x : f[x]=find(f[x]); } inline void sol(){ int n, m; cin >> n >> m; for(int i=1;i<=n;i++) f[i]=i,a[i]=0; bool flag=1; int cnt=0; for(int i=1;i<=m;i++){ int x, y; cin >> x >> y; ++a[x], ++a[y]; int fx = find(x), fy=find(y); f[fx]=fy; if(fx==fy) flag=0; } if(m+1!=n||!flag){puts("No"); return ;} for(int i=1;i<=n;i++) if(a[i]>2) cnt++; if(cnt<=1) puts("Yes"); else puts("No"); } int main(){ IO; int ttt; cin >> ttt; while(ttt--) sol(); return 0; }
I题 笑声比赛
【题意】给定一长度为n的字符串,询问m次,每次询问给出两个字符X和Y和一个长度L,问以XY结尾且长度为L的子序列共有多少个,结果对1e9+7取模。
【思路】动态规划,取字符串最后出现的XY,往前寻找长度为 len - 2 的子序列,用last记录上一个a[i]位置,则状态转移方程如下:(不要忘记取模技巧~)
1、如果last[a[i]] = 0, f[i][j] = f[i-1][j] + f[i-1][j-1];
2、否则f[i][j] = f[i-1][j] + f[i-1][j-1] - f[last[a[i]] - 1][j-1]
#include <bits/stdc++.h> #define IO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) #define ll long long using namespace std; const ll N=1e3+5, mod=1e9+7; ll f[N][N]={{0}}, last[N], pos[27]; inline void sol(){ int n; cin >> n; string s; cin >> s; int siz = s.size(); memset(pos,0,sizeof pos); memset(f,0,sizeof f); for(int i = 0; i < siz; i++) last[i+1]=pos[s[i]-97], pos[s[i]-97]=i+1; f[0][0]=1; for(int i=1;i<=n;i++){ f[i][0]=1; for(int j=1;j<=i;j++){ f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod; if(last[i]!=0) f[i][j]=(f[i][j]-f[last[i]-1][j-1]+mod)%mod; } } int q; cin >> q; while(q--){ char x, y; cin >> x >> y; int len; cin >> len; size_t tmp = s.rfind(y); tmp = s.rfind(x,tmp); if((int)tmp<len-2){cout << "0\n"; continue;} else cout << f[tmp][len-2]%mod << endl; } } int main(){ IO; int ttt; cin >> ttt; while(ttt--) sol(); return 0; }
J题 行走之谜
【题意】没看懂,放一下原题,估计是不会补了。