4.27比赛总结
文章目录
- T1
- T2
- 法一:倍增求 LCA
- 法二:Dijkstra 求最短路
- 法三:dfs 求深度
- T3
- T4
- 总结
T1
一道非常简单的题,结果我因为一句话没写挂了 80pts……
题目中没写 a a a 数组要按照 b b b 数组的顺序,所以对于最大方案,我们需要从最小的开始,这样后面的才有可能填上。
所以一开始我们要先对 b b b 数组从小到大排序(我就是少写了这一行……),然后我们就可以开始算方案数了,怎么算呢?
很简单,首先对于 a 1 a_1 a1,它前面没有任何数,所以它能取的方案书就是 b 1 b_1 b1,然后思考 a 2 a_2 a2,它前面已经有一个数被选了(因为从小到大排序后后面的数一定包含了前面的数),所以对于 a 2 a_2 a2 来讲它有 b 2 − 1 b_2-1 b2−1 种选择,然后是 a 3 , a 4 … a_3,a_4\dots a3,a4…,一直到 a n a_n an,这时我们很容易发现:对于 a i a_i ai 来讲,它有 b i − i + 1 b_i-i+1 bi−i+1 种选择,再根据乘法原理可知答案是:
a n s = ∏ i = 1 n b i − i + 1 ans=\prod_{i=1}^nb_i-i+1 ans=i=1∏nbi−i+1
然后直接输出答案即可。
(老规矩。)
T2
这道题很简单,这里我提供三种做法。
在此之前,我们首先要确定一件事:如果牛郎与织女之间的距离是一个奇数,那么他们会在桥上,否则会在星球上。接下来就是怎么求距离的问题了。
法一:倍增求 LCA
我们可以通过倍增求 LCA 并记录下距离,代码很简单,时间复杂度: O ( Q log 2 ( N ) ) O(Q\log_2(N)) O(Qlog2(N))。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,l,depth[100006],st[100006][26];
vector<int>v[100006];
void LCA(int x,int y)
{if(depth[x]<depth[y]){swap(x,y);}for(int i=20;i>=0;i--){if(depth[st[x][i]]>=depth[y]){x=st[x][i];l+=(1<<i);}}//这里为了节省时间,我直接把后面求 LCA 的过程省略了。//因为后面的跳动都是两边同时跳,那么每次加的距离都是偶数,而加偶数并不会改变它的奇偶性。
}
void dfs(int x,int deep,int fa)
{st[x][0]=fa;depth[x]=deep;for(auto i:v[x]){if(i==fa){continue;}dfs(i,deep+1,x);}
}
signed main()
{// freopen("bridge.in","r",stdin);// freopen("bridge.out","w",stdout);cin>>n>>q;for(int x,y,i=1;i<n;i++){cin>>x>>y;v[x].emplace_back(y);v[y].emplace_back(x);}dfs(1,1,0);for(int i=1;i<=20;i++){for(int j=1;j<=n;j++){st[j][i]=st[st[j][i-1]][i-1];}}while(q--){int x,y;l=0;cin>>x>>y;LCA(x,y);if(l&1){cout<<"Y"<<endl;}else{cout<<"N"<<endl;}}return 0;
}
法二:Dijkstra 求最短路
我们可以把它看作一张图,然后直接跑最短路。
(我没有代码,读者自己写吧……)
法三:dfs 求深度
我们可以把两点间距离,看作是两点间的深度之和,然后判断奇偶。
代码(题解的):
#include <bits/stdc++.h>
#include <stdio.h>
using namespace std;
int n, q, a, b;
vector<vector<int>> s(1e5 + 1);
vector<int> d(1e5 + 1, 1e5);
void dfs(int x, int y) {d[x] = y;for (auto p : s[x]) {if (d[p] > y + 1) {dfs(p, y + 1);}}
}
int main() {cin >> n >> q;for (int i = 0; i < n - 1; i++) {cin >> a >> b;s[a].push_back(b);s[b].push_back(a);}dfs(1, 0);for (int i = 0; i < q; i++) {cin >> a >> b;cout << (((d[a] + d[b]) % 2 == 0) ? "N" : "Y") << endl;}
}
三种方法,任你选择。
T3
一道不是很简单的题……
我的基础想法是用 tarjan 缩点然后再做,但是图不连通,那么跑 tarjan 的时间复杂度就到了 O ( n 2 ) O(n^2) O(n2),所以明显不行。
然后我又思考过跑 dfs 来找连通块,结果时间复杂度还是 O ( n 2 ) O(n^2) O(n2)。
于是就只有一种方法了:并查集!
然后我们就可以标记连通块了。
当然,我们肯定要用个 vector
保存下来每个连通块内的点,接下来又该怎么做呢?
我们要代价最小,说白了就是找离 i i i 最近的点 j j j,然后我就有了一个想法:二分!(从时间复杂度上也看得出来是 O ( T × N log 2 ( N ) ) O(T\times N\log_2(N)) O(T×Nlog2(N)))
我们可以用 lower_bound
找出第一个大于等于 i i i 的点,那么它的上一个就是最后一个小于 i i i 的点,这两个点就必然是离 i i i 最近的两个点,然后记录下来就行了。
我们要让 1 1 1 能到 n n n 连通,那么我们就只需要稍微改一下上面的过程:在包含的 1 1 1 这个连通块里面二分找第 i i i 个点的 lower_bound
。
因为二分需要单调性,所以我们可以在这之前对每个连通块里面的点排序一下,但这明显会 T L E \color{blue}{TLE} TLE,又该怎么优化呢?
很简单,我们只需要请出排序的“老大哥”——set
。我们把 vector
换成 set
就行了。
然后就水灵灵的 A C \color{green}{AC} AC 了!
代码:
#include<bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
int t,n,m,num,ans=INF,fa[100006],col[100006],b[100006],cost[2][100006];
set<int>s[100006];
int find(int x)
{if(fa[x]==x){return x;}return fa[x]=find(fa[x]);
}
signed main()
{cin>>t;while(t--){ans=INF;//初始化!!!for(int i=1;i<=num;i++){s[i].clear();}num=0;memset(b,0,sizeof(b));memset(col,0,sizeof(col));cin>>n>>m;for(int i=1;i<=n;i++){fa[i]=i;}for(int i=1,x,y;i<=m;i++){cin>>x>>y;int xb=find(x);int yb=find(y);if(xb!=yb){fa[yb]=xb;}}if(find(1)==find(n)){cout<<0<<endl;continue;}for(int i=1;i<=n;i++){if(!b[find(i)]){b[find(i)]=col[find(i)]=++num;s[num].insert(i);}else{col[find(i)]=b[find(i)];s[col[find(i)]].insert(i);}}memset(cost,INF,sizeof(cost));for(int i=1;i<=n;i++){if(find(i)==find(1)){continue;}auto it=s[col[find(1)]].lower_bound(i);auto it1=it;if(it1==s[col[find(1)]].end()){it1--,it--;}else if(it1==s[col[find(1)]].begin());else{it--;}cost[0][col[find(i)]]=min({cost[0][col[find(i)]],((*it)-i)*((*it)-i),((*it1)-i)*((*it1)-i)});}for(int i=1;i<=n;i++){if(find(i)==find(n)){continue;}auto it=s[col[find(n)]].lower_bound(i);auto it1=it;if(it1==s[col[find(n)]].end()){it1--,it--;}else if(it1==s[col[find(n)]].begin());else{it--;}cost[1][col[find(i)]]=min({cost[1][col[find(i)]],((*it)-i)*((*it)-i),((*it1)-i)*((*it1)-i)});}for(int i=1;i<=n;i++){if(find(i)==find(1)){continue;}else if(find(i)==find(n)){ans=min(ans,cost[0][col[find(n)]]);}else{ans=min(ans,cost[0][col[find(i)]]+cost[1][col[find(i)]]);}}cout<<ans<<endl;}return 0;
}
当然,这份代码稍微复杂了点,看看题解代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 100010
int n, m, i, j, k;
set<int>s[N];
set<int>::iterator it, jt, kt;
int f[N], p[N], u, v, a, b, ans, t;
int fa(int x) {return f[x]==x ? x : f[x]=fa(f[x]);
}
int pingfang(int x) {return x*x;
}
int len(int x, int y) {int ans=1e18;for(it=s[x].begin(); it!=s[x].end(); ++it) {jt=s[y].lower_bound(*it);if(jt!=s[y].end()) ans=min(ans, pingfang((*jt)-*it));if(jt!=s[y].begin()) ans=min(ans, pingfang((*(--jt))-*it));}return ans;
}
signed main() {scanf("%lld", &t);while(t--) {scanf("%lld%lld", &n, &m);for(i=1; i<=n; ++i) f[i]=i;for(i=1; i<=n; ++i) s[i].clear();for(i=1; i<=m; ++i) scanf("%lld%lld", &u, &v), f[fa(u)]=fa(v);for(i=1; i<=n; ++i) s[fa(i)].insert(i);a=f[1];b=f[n];ans=len(a, b);for(i=1; i<=n; ++i) {if(f[i]==a||f[i]==b||f[i]!=i) continue;ans=min(ans, len(i, a)+len(i, b));}printf("%lld\n", ans);}return 0;
}
真简略……
T4
我的评价:特别简单但是很迷惑人……
这题看上去很复杂,实则一点也不复杂。
对于 50pts,我们可以这么思考:如果两个点之间可以互相运送垃圾,则连上一条边,那么同一个连通分量中,所有的垃圾都可以汇集到一个点上,所以问题等价于询问图中有多少个连通分量。
但是这样做最坏情况下时间复杂度为 O ( n 2 ) O(n^2) O(n2),所以我们肯定要换种方法。
(由于作者实在不是很会描述(懒),于是贴上了一份题解。)
我们对所有点按照 x x x 坐标排序,如果前面的点中 y y y 的最小值小于当前点,我们则对这两个点连边。
同时我们倒序遍历,如果后面点的 y y y 的最大值大于当前点,我们则对这两个点连边。
这样边的数量就降为了 O ( n ) O(n) O(n)。
(说实话,我也没太看懂……只是大致有感觉。)
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x[100006],y[100006],mny[100006],mxy[100006],a[100006];
bool cmp(int xx,int yy)
{return x[xx]<x[yy]||x[xx]==x[yy]&&y[xx]<y[yy];
}
signed main()
{cin>>n;for(int i=1;i<=n;i++){cin>>x[i]>>y[i];a[i]=i;}sort(a+1,a+n+1,cmp);mny[1]=y[a[1]];for(int i=2;i<=n;i++){mny[i]=min(mny[i-1],y[a[i]]);}mxy[n]=y[a[n]];for(int i=n-1;i>=1;i--){mxy[i]=max(mxy[i+1],y[a[i]]);}int ans=1;for(int i=1;i<n;i++){if(mny[i]>mxy[i+1]){ans++;}}cout<<ans;return 0;
}
总结
- T1: 本来应该 A C \color{green}{AC} AC 的,结果挂了 80pts……
- T2: 完美 A C \color{green}{AC} AC!
- T3: 理论上来讲可以拿到 50pts,但因为一些奇奇怪怪的计算机底层问题而 W A \color{red}{WA} WA 了……(还有 T L E \color{blue}{TLE} TLE。)
- T4: 根本不会,直接输出了样例,骗到 8pts。
总分:129pts,满分 400pts,反正我不满意。