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,反正我不满意。