2020牛客暑期多校训练营(第二场)

总结:
这次做题状态不佳,签到十分钟,苦恼五小时。把签到题做完后,苦于F题,在想暴力做法,但是没做出来,看了视频讲解才知道是二次单调队列,还是需要补很多很多学习不深的点。做C题的时候,对树链的理解不深,理解有误,后来专门补了相关的知识。

B

题意

给出平面点坐标(不含原点),找到一个原点在边界上的圆,使尽可能多的点在这个圆上,求在圆边界上的点的数量最大值。点的坐标不重复,不含原点。

思路

所求圆边界上至少有两个点。根据三点共圆,所求圆必过原点O(0,0),枚举另外的两个点,计算出圆心坐标,统计相同的圆心坐标数量,圆心坐标相同数量越多,此时在题目所求圆的边界上的点越多。
三点共圆公式参考
https://www.cnblogs.com/xpvincent/p/8266734.html

代码

#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false),cin.tie(0);
#define ll long long
#define inf 0x3f3f3f3f
const int N=1e5+5;
//set<string>b;
//set<string>::iterator it;
struct node
{
	ll x,y;
	node(int a=0,int b=0):x(a),y(b){}
}a[N];
int ans=0;
double sq(ll x,ll y)
{
	return x*x+y*y;
}
map< pair<double,double>,int>p;
void fun(node a,node b,node c)
{
	ll ta=sq(a.x,a.y),tb=sq(b.x,b.y),tc=sq(c.x,c.y);
	double A=a.x*(b.y-c.y)-a.y*(b.x-c.x)+b.x*c.y-c.x*b.y;
	double B=ta*(b.y-c.y)+tb*(c.y-a.y)+tc*(a.y-b.y);
	double C=ta*(c.x-b.x)+tb*(a.x-c.x)+tc*(b.x-a.x);
	double x=-B/(2*A),y=-C/(2*A);
	p[make_pair(x,y)]++;
	if(p[make_pair(x,y)]>ans) ans=p[make_pair(x,y)];
}
int main()
{
    IO;
    int n,i,j;
    node O=node(0,0);
    cin>>n;
    for(i=1;i<=n;i++)
    {
    	cin>>a[i].x>>a[i].y;
	}
	for(i=1;i<=n;i++)
	{
		p.clear();
		for(j=i+1;j<=n;j++)
		{
			if(a[i].x*a[j].y==a[i].y*a[j].x) continue;
			fun(O,a[i],a[j]);
		}
	}
    cout<<ans+1<<endl;
    return 0; 
}


C

题意

给你一颗无根树,让你求最少的树链,使得每条边都被至少一条树链经过,输出树链的条数和每条树链的两个端点,如果有多个答案可以输出任意一组解。

思路

很显然,这个题的解所选节点肯定是叶子节点,因为如果其不为叶子节点,则存在其的子节点所包含的边数大于当前节点。然后我们先求出所有的叶子节点,然后进行两两配对,最后如果还剩下一个数,随便和谁配对都行。至于配对的原则,一开始想的是直接找其兄弟节点,找到谁就和谁配对,结果发现不对。
在这里插入图片描述
如图,若选择{1,2},{3,4},则根节点的两个边未被选中。
故考虑让a[i]和a[i+(k+1)/2]匹配(其中,k为根节点个数),这样可以保证覆盖边数最多。

代码

#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int N=2e5+5;
vector<int>e[N];
int a[N],k=0;
void dfs(int u,int fa)
{
    for(auto v:e[u])
        if(v!=fa)
            dfs(v,u);
    if(e[u].size()==1)
        a[++k]=u;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1,v,u;i<=n-1;i++)
    {
        scanf("%d %d",&u,&v);
        e[u].pb(v);
        e[v].pb(u);
    }
    int root=1;
    dfs(root,0);
    int mid=(k+1)/2;
    vector<pii>v;
    for(int i=1;i<=k-mid;i++)
        v.pb(mp(a[i],a[i+mid]));
    if(k&1)
        v.pb(mp(a[mid],root));
    cout<<v.size()<<endl;
    for(auto i:v)
        cout<<i.first<<' '<<i.second<<endl;
        return 0;
}

F

题意

输入三个整数m, n, k构建一个m × n的矩阵A,A[m][n]所存的数为m,n的最大公倍数,求矩阵A中所有k × k的子矩阵中的最大值的和。

思路

两次单调队列的解法,思路为先用一个一维队列找出每一行长度为k区间的最大值,在通过另一个二维的队列找出列的最大值,最后求和即为答案。

代码

#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
typedef long long ll;
int findtheans(int A,int B)
{
	int muli=A*B;
	while(B)
    {
        int tmp=A%B;
        A=B;
        B=tmp;
    }
    return muli/A;

}
int n, m, k;
int A[5005][5005], tar[5005], a[5005][5005];
int main()
{
    cin>>n>>m>>k;
    int i,j;
    ll ans = 0;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
        {
        	A[i][j]=findtheans(i,j);
		}  
    }
    for(i=1;i<=n;i++)
    {
        int l=0,r=0;
        for(j=1;j<=m;j++)
        {
            while(l<r&&j-tar[l]+1>k)
                l ++;
            while(l<r&&A[i][tar[r-1]]<=A[i][j])
                r --;
            tar[r++]=j;
            a[i][j]=A[i][tar[l]];
        }
    }
    for(int j=1;j<=m;j++)
    {
        int l=0,r=0;
        for(int i=1;i<=n;i++)
        {
            while(l<r&&i-tar[l]+1>k)
			l++;
            while(l<r&&a[tar[r-1]][j]<=a[i][j])
			r--;
            tar[r++]=i;
            a[i][j]=a[tar[l]][j];
        }
    }
    for(int i=k;i<=n;i++)
    {
        for(int j=k;j<=m;j++)
        {
            ans+=a[i][j];
        }
    }
    cout <<ans;
    return 0;
}

G

题意

给出a、b两个串,长分别为n,m,求a中有多少长为m的连续子串t,使得ti>=bi

思路

对于每一个 Ai 需要求一个长度为 m 的 bitset Si,发现,在对 B 序列排序后,如果求出 B 对应的 bitset Sj 最后在Si 中二分找到 A[i] 在 B 中的位置即可。

代码

#include<bits/stdc++. h>

using namespace std;

typedef long long LL;

const int Inf=0x3f3f3f3f;
const double eps=1e-7;
const int maxn=2e5+50;
const int maxm=4e4+50;

bitset<maxm> cur,S[maxm];
vector< pair<int,int> > vc;
int a[maxn], b[maxm];
int n,m;

int find(int x){
    if(x < vc[0].first) return 0;
    int l=0,r=m-1, ret=0;
    while (l<=r){
        int mid=(l+r)>>1;
        if(vc[mid].first <= x){
            ret = mid;
            l = mid+1;
        }
        else 
            r = mid-1;
    }
    return ret+1;
}   

int main()
{
    cin>>n>>m;
    for(int i=1; i<=n; i++) scanf("%d",&a[i]);
    for(int i=1; i<=m; i++){
        scanf("%d",&b[i]);
        vc.push_back({b[i], i});
    }
    sort(vc.begin(), vc.end());
    for(int i=0; i<vc.size(); i++){
        S[i+1] = S[i];
        S[i+1].set( vc[i].second );
    }

    for(int i=n; i>n-m+1; i--){
        cur = cur>>1;
        cur.set(m);
        cur = cur&S[find(a[i])];
        // printf("(%d\n",find(a[i]));
    }
    int ans=0;
    for(int i=n-m+1; i>=1; i--){
        cur = cur>>1;
        cur.set(m);
        cur = cur&S[find(a[i])];
        ans += cur[1];
            // cout<<" [ "<<cur<<endl;
    }
    cout<<ans<<endl;
}



J

题意

给一个排列A={1,2,3,4,5…n },将它进行依照P序列k次置换,置换后变成排列B。
补充:这里置换的概念是将第i位的元素替换到第j位,被替换的位置j必须与其他位替换(不是原本理解的双方交换而是单方面的移动)。

思路

理解了置换后,很容易想到这个置换必须由若干个简单环构成,否则必然存在一个/多个元素丢失,不符合题意,所以我们可以用dfs求环的大小,假设环的大小为len,那么转换k次就相当于转换了k%len次,我们不妨设t=k%len。假设我现在进行了x次操作,那么如果刚好是(xt)%len==1即xk%len==1,求出逆元x即符合题意,构造时,如果b数组表示答案,c为环中元素,那么就有b[c[i]]=c[(i+x)%len]。
参考博客:https://www.cnblogs.com/whitelily/p/13296935.html

代码

#include<iostream>
#include<vector>
#define ll long long
using namespace std;
int n,k,to[100007],vis[100007],ans[100007];
int a[100007];
vector<int>ho;
void dfs(int p){      			//求环 
	vis[p]=1;
	ho.push_back(a[p]);
	if(vis[to[p]]==0){
		dfs(to[p]);
	}
}
void go(){
	int len=ho.size(),inv;  
	for(int i=0;i<len;i++){  	//遍历求逆元
		if((ll)i*k%len==1){
			inv=i;
			break;
		}
	}
	for(int i=0;i<len;i++){  	//按照 b[c[i]]=c[(i+x)%len]对答案进行构造 
		ans[ho[i]]=ho[(i+inv)%len];
	}
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		to[i]=a[i];
	}
	for(int i=1;i<=n;i++){
		ho.clear();  				//每次在执行前都要把存环的vector清空 
		if(vis[i]==0){
			dfs(i);
			go();
		}
	}
	for(int i=1;i<=n;i++){
		printf("%d ",ans[i]);
	}puts("");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值