1. 程式人生 > 實用技巧 >7.13 2020牛客暑期多校訓練營(第二場)題解及補題

7.13 2020牛客暑期多校訓練營(第二場)題解及補題

目錄

7.13 2020牛客暑期多校訓練營(第二場)題解及補題

比賽過程

D題是簽到題,後面做C和H,實在有點可惜,方法使用錯誤超時結束。C應該正確使用dfs序,H選取右小角的6*6區域暴力。

題解

B Boundary

題目連結

題意

給定n個點,讓你找最多有多少個點共圓並且該圓過原點。

解法

用求三角形外心的模板。遍歷選取兩個點和原點組成三角形,求三角形的外心並將點存起來,找出被覆蓋最多次數的點即為圓心,是在圓上的點組成的多邊形的邊數,得到邊數後求(int)sqrt(ans*2) +1就是答案。

程式碼

#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
using namespace std;
#define IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int inf = 0x3f3f3f3f;
typedef long long ll;
const int maxn = 2000500;
const ll mod = 1e9 + 7;
typedef pair<int,int> pii;
const double pi = acos(-1);
typedef long long ll;
typedef pair<double, double> pdd;
const int N = 2005;
struct point {
    ll x, y;
}a[N];
vector<pdd> v;
//求三角形外心
void getc(const point& p1, const point& p2, const point& p3, pdd& center) {
	ll x1 = p1.x; 
	ll x2 = p2.x;
	ll x3 = p3.x;
	ll y1 = p1.y;
	ll y2 = p2.y;
	ll y3 = p3.y;

	ll t1=x1*x1+y1*y1;
	ll t2=x2*x2+y2*y2;
	ll t3=x3*x3+y3*y3;
    
	double temp=x1*y2+x2*y3+x3*y1-x1*y3-x2*y1-x3*y2;
    
    center.first = double(t2*y3+t1*y2+t3*y1-t2*y1-t3*y2-t1*y3)/temp;
	center.second = double(t3*x2+t2*x1+t1*x3-t1*x2-t2*x3-t3*x1)/temp;
}

int main() {
    IO;
    int n;
    cin>>n;
    for(int i = 1; i <= n; ++ i)
        cin>>a[i].x>>a[i].y;
    point temp;
    temp.x = 0, temp.y = 0;
    for(int i = 1; i <= n; ++ i) {
        for(int j = i + 1; j <= n; ++ j) {
            pdd c;
            if(a[i].x * a[j].y != a[i].y * a[j].x) {
                getc(a[i], a[j], temp, c);
                v.push_back(c);
            }
        }
    }
    sort(v.begin(),v.end());
    int ans = 0, cnt = 0;
    pdd tmp = v[0];
    for(auto i: v) {
        if(i == tmp) {
            cnt++;
        }
        else {
            ans = max(ans, cnt);
            cnt = 1;
            tmp = i;
        }
    }
    ans = max(cnt, ans);
    cout<<int(sqrt(ans*2))+1;
    return 0;
}

C Cover the Tree

題目連結

題意

題目給了一個無根樹,要求找到最小數量的鏈,使得樹上任意一個邊至少被一個鏈包含,
輸出鏈的最小數目ans,以及每一個鏈的第一個結點以及最後一個結點。

解法

要求找最小數目的鏈包含所有的邊,貪心的思想肯定是找兩端是葉子節點的鏈,那麼統計一下葉子節點的數目cnt,如果是奇數個葉子結點,那麼ans就是(cnt+1)/2,如果是偶數個葉子節點,那麼ans就是cnt/2。
n<=2的時候答案顯然,n>=3的時候,取任意一個非葉子節點作為根(這裡我們選取了度數最多的點作為根),然後跑葉子節點的dfs序,那麼假設cnt為偶數,我們構造的cnt/2條鏈就是l1-lcnt/2+1,l2-lcnt/2+2,……,lcnt/2-lcnt,如果cnt為奇數,那麼只需要把最後一個剩下的葉子節點和根節點連線即可。

程式碼

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
vector<int> g[200010];
int dfs[200020];//dfs序 陣列
int len,tim;// len 是當前dfs序的長度, tim 是時間
int s[200020],e[200020];//  存的某點子樹對應區間的起點 和終點, 標號i表示某點dfs序中的位置
int pos[200020];//某點i的 dfs序位置 
int d[200020];


void DFS(int u,int fa)
{
    int x=len+1;
    if(d[u]==1){
	    s[++len]=++tim;// 起點是開始時間
	    dfs[len]=u;
	    pos[u]=len;	
	}    
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        if(g[u][i]!=fa)//不能dfs遍歷父親
        {
            DFS(g[u][i],u);
        }
    }
    e[x]=tim;// 結束時間 即為以他為子樹的終止區間
}
int main(){
	scanf("%d",&n);
	int m=n-1;
	memset(d,0,sizeof(d));
	int st=-1,stt=0;
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		g[l].push_back(r);
		g[r].push_back(l);
		d[l]++;
		d[r]++;
		if(d[l]>stt){
			stt=d[l];
			st=l;
		}
		if(d[r]>stt){
			stt=d[r];
			st=r;
		}
	}
	DFS(st,0);
	cout<<(len+1)/2<<endl;
	if(len&1){
        for(int i=1;i<=len/2;i++){
            cout<<dfs[i]<<" "<<dfs[i+len/2]<<endl;
        }
        cout<<st<<" "<<dfs[len]<<endl;
    }
    else{
        for(int i=1;i<=len/2;i++){
            cout<<dfs[i]<<" "<<dfs[i+len/2]<<endl;
		}
	}
}

D Duration

題目連結

題意

題目給了同一天中的兩個時間點,格式為HH:MM:SS,然後要求輸出兩個時間點之間的秒數。

解法

簽到題,只需要稍微計算一下即可。

程式碼

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int inf = ~0u >> 1;
typedef pair<int,int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n) - 1; i >= a; --i)
int main() {
    IO;
    int h1, h2, m1, m2, s1, s2;
    scanf("%d:%d:%d", &h1, &m1, &s1);
    scanf("%d:%d:%d", &h2, &m2, &s2);
    int sum1 = h1 * 3600 + m1 * 60 + s1;
    int sum2 = h2 * 3600 + m2 * 60 + s2;
    int ans = abs(sum1 - sum2);
    cout << ans << endl;
    return 0;
}

F Fake Maxpooling

題目連結

題意

題目給了一個nm的矩陣,矩陣中Ai,j=lcm(i,j),然後題目要輸輸出所有KK的子矩陣的最大值之和。

解法

首先跑一下nm的矩陣,然後當k<=6的時候用純暴力即可,k>=7的時候,我們只需要貪心的尋找每一個KK的子矩陣的右下角的6*6的小矩陣求最大值即可。

程式碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 5005
ll n,m,k;
ll a[maxn][maxn];
ll gcd(ll b,ll c){return c==0?b:gcd(c,b%c);}
ll lcm(ll b,ll c){return b * c/ gcd(b, c);}

int main(){
	scanf("%lld%lld%lld",&n,&m,&k);
	for(ll i=1;i<=5000;i++){
		for(ll j=i;j<=5000;j++){
			a[i][j]=lcm(i,j);
			a[j][i]=a[i][j];
		}
	}
	ll ans=0;
	if(k<=6){
		for(ll i=0;i<=n-k;i++){
			for(ll j=0;j<=m-k;j++){
				ll tmp=0;
				for(int w=1;w<=k;w++){
					for(int e=1;e<=k;e++){
						tmp=max(tmp,a[i+w][j+e]);
					}
				}
				ans+=tmp;;
			}
		}	
	}else{
		int l=-1,r=-1;
		for(ll i=0;i<=n-k;i++){
			for(ll j=0;j<=m-k;j++){
				ll tmp=0;
				for(int w=k-5;w<=k;w++){
					for(int e=k-5;e<=k;e++){
						tmp=max(tmp,a[i+w][j+e]);
					}
				}
				ans+=tmp;;
			}
		}
		
	}
	cout<<ans<<endl;

}

J Just Shuffle

題目連結

題意

對於一個排列A,給定一個置換規則P,在使用置換P K 次後得到新的排列B

解法

(置換群概念理解為博主Yoangh原創內容:連結

首先給你一個序列,假如:
s = {1 2 3 4 5 6}
然後給你一個變換規則
t = {6 3 4 2 1 5}
就是每一次按照t規則變換下去
比如這樣
第一次:6 3 4 2 1 5
第二次:5 4 2 3 6 1
第三次:1 2 3 4 5 6
發現經過幾次會變換回去,在變換下去就是迴圈的了,這就是一個置換群。
我們可以這樣表示一個置換群,比如按照上面變化規則
1->6->5->1 那麼這些是一個輪換
2->3->4->2 這些是一個輪換
所以可以寫為
t = { {1 6 5},{ 2 3 4 } }

解法:

A ^ K = B
且P等於A再置換一次
我們設Z為K的逆元,r為置換迴圈節,則 B ^ Z = A
//逆元定義如下:

Z * K % r == 0 ,(r為置換迴圈節)
令Z:Z * K % r == 1
求出Z,然後讓B置換Z次即可得A。

程式碼

#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
using namespace std;
#define IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int inf = 0x3f3f3f3f;
typedef long long ll;
const int maxn = 2000500;
const ll mod = 1e9 + 7;
typedef pair<int,int> pii;
const double pi = acos(-1);
typedef long long ll;
const int N = 1e5 + 1;
int a[N], b[N], c[N];
vector <int > v;

void solve(int k)
{
	int i, j;
	int r = v.size(), inv;
	for(i = 0; i < r; i ++)
		if((ll)k * i % r == 1)
			inv = i;
	for(j = 0; j < r; j ++)
		c[v[j]] = v[(j + inv) % r];
}

int main() {
    IO;
	int n, k;
	cin>>n>>k;
	for(int i = 1; i <= n; i ++)
		cin>>b[i];
	for(int i = 1; i <= n; i ++) {
		if(!a[i]) {
			v.clear();
			int x = b[i];
			while(!a[x]) {
				a[x] = 1;
				v.push_back(x);
				x = b[x];
			}
			solve(k);
		}
	}

	for(int i = 1; i <= n; i ++) {
		if(i != n)
			cout<<c[i]<<" ";
		else
			cout<<c[i]<<endl;
	}
	return 0;
}