4.27比赛总结

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 b21 种选择,然后是 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 bii+1 种选择,再根据乘法原理可知答案是:

a n s = ∏ i = 1 n b i − i + 1 ans=\prod_{i=1}^nb_i-i+1 ans=i=1nbii+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;
}

总结

  1. T1: 本来应该 A C \color{green}{AC} AC 的,结果挂了 80pts……
  2. T2: 完美 A C \color{green}{AC} AC
  3. T3: 理论上来讲可以拿到 50pts,但因为一些奇奇怪怪的计算机底层问题而 W A \color{red}{WA} WA 了……(还有 T L E \color{blue}{TLE} TLE。)
  4. T4: 根本不会,直接输出了样例,骗到 8pts。

总分:129pts,满分 400pts,反正我不满意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值