2020牛客暑期多校训练营(第二场)解题报告

D

温暖的签到题。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

ll h1,m1,s1;
ll h2,m2,s2;

int main(){
	h1=input(),m1=input(),s1=input();
	h2=input(),m2=input(),s2=input();
	ll t2=h2*3600+m2*60+s2;
	ll t1=h1*3600+m1*60+s1;

	printf("%lld\n",abs(t2-t1));
}

C

构造题。基本原理与这里的K现当。一个结论是答案一定是叶子节点的一半,一个可行的方案一定是不同子树的叶子节点互相连接。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

#define PII pair <int,int>
#define fr first
#define sc second
#define mp make_pair
#define pb push_back

const int N=4e5+7;

vector<int> G[N];
int cnt[N];
int n,h;
vector<int> DFS;
vector<PII> Ans;

void dfs(int u,int fa){
	if(cnt[u]==1) DFS.pb(u); 
	for(auto v:G[u]){
		if(v==fa) continue;
		dfs(v,u);
	}
}

int main(){
	n=input();

	for(int i=1;i<n;i++){
		int u=input(),v=input();
		G[u].pb(v),G[v].pb(u);
		cnt[u]++,cnt[v]++;
	}

	dfs(1,0);

	int t=DFS.size();

	for(int i=0;i<t/2;i++){
		Ans.pb(mp(DFS[i],DFS[i+t/2]));
	}
	if(t%2) Ans.pb(mp(DFS[t/2-1],DFS[t-1]));

	printf("%d\n",Ans.size());
	for(auto v:Ans){
		printf("%d %d\n",v.fr,v.sc);
	}
}

F

跟队友在这个题互相演😂。

裸的二维区间最值的题目,我们首先使用单调队列求出矩阵中每个元素向右延伸\(K\)位的最大值\(t_{ij}\),接着再次使用单调队列求出每个\(t_{ij}\)向下延伸\(K\)位的最大值,即可求得所有的二维区间最值。对于构造出\(lcm(i,j)\)矩阵,我们可以考虑记忆化来降低复杂度,避免超时。

#include <bits/stdc++.h>
using namespace std;

#define N 5010
#define ll long long

int h[N][N], max1d[N][N];
int n, m, k;

ll solve(int a,int b){
	int tmp[N];
	for(int j=1;j<=m;j++){
		int l=1,r=0;
		for(int i=1;i<a;i++){
			while(r>=l&&h[tmp[r]][j]<=h[i][j]) r--;
			tmp[++r]=i;
		}

		for(int i=1;i+a-1<=n;i++){
			while(r>=l&&h[tmp[r]][j]<=h[i+a-1][j]) r--;
			tmp[++r]=i+a-1;
			while(tmp[l]<i) l++;
			max1d[i][j]=h[tmp[l]][j];
		}
	}

	ll ans=0;
	for (int i=1;i+a-1<=n;i++) {
		int l=1,r=0;
		for (int j=1;j<b;j++) {
			while (r>=l&&max1d[i][tmp[r]]<=max1d[i][j]) r--;
			tmp[++r]=j;
		}

		for (int j=1;j+b-1<=m;j++) {
			while(r>=l&&max1d[i][tmp[r]]<=max1d[i][j+b-1]) r--;
			tmp[++r]=j+b-1;
			while(tmp[l]<j) l++;
			ll tmax=max1d[i][tmp[l]],ii=i+a-1,jj=j+b-1;
			ans+=tmax; 
		}
	}
	return ans;
}

int lcm(int i,int j){
	return 1LL*i*j/__gcd(i,j);
}

int main() {
	scanf("%d%d%d", &n, &m, &k);

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(j<=n&&i<=m&&h[j][i]) h[i][j]=h[j][i];
			else h[i][j]=lcm(i,j);
		}
	}

	printf("%lld\n",solve(k,k));
	return 0;
}

A

神仙队友做的。我贡献了hash_map😁。

对于本题我们不妨枚举所有串的后缀,用map保存某一后缀的个数,由于直接用string类型会超时,所以我们计算hash值保存,然后我们通过枚举字符串的前缀,我们可以通过map知道有多少个后缀跟前缀相同,那么对答案的贡献是\(len^2*cnt_{与该前缀相等的后缀}\),但是这样算出来的答案是不正确的,因为一个前缀对答案不知贡献一次(例如前缀"aba"和后缀"ba"再"a"和“ab"会分别计算一次答案),考虑去重,我们可以利用kmp的next数组从前往后计算\(cnt_{next[j]}-=cnt_j\),之后便可直接计算答案了。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

#define PII pair <ll,ll>
#define fr first
#define sc second
#define mp make_pair

const int N=1e6+7;
ll base[2]={19260817,(int)1e5+7},mod[2]={(int)1e9+7,998244353};

ll p[2][N],h[2][N];

void init(){
	p[0][0]=p[1][0]=1;
	for(int i=1;i<N;i++){
		p[0][i]=p[0][i-1]*base[0]%mod[0];
		p[1][i]=p[1][i-1]*base[1]%mod[1];
	}
}

void str_hash(string s){
	h[0][0]=s[0]%mod[0];
	h[1][0]=s[0]%mod[1];
	for(int i=1;i<s.length();i++){
		h[0][i]=(h[0][i-1]*base[0]+s[i])%mod[0];
		h[1][i]=(h[1][i-1]*base[1]+s[i])%mod[1];
	}
}

PII gethash(int l,int r){
	return mp(((h[0][r]-h[0][l-1]*p[0][r-l+1])%mod[0]+mod[0])%mod[0],((h[1][r]-h[1][l-1]*p[1][r-l+1])%mod[1]+mod[1])%mod[1]);
}

const ll mod1=1e9+7;
const int maxsz=3e6+7;

template<typename key,typename val>
class hash_map{public:
  struct node{key u;val v;int next;};
  vector<node> e;
  int head[maxsz],nume,numk,id[maxsz];
  int geths(PII &u){
	int x=(1ll*u.fr*mod1+u.sc)%maxsz;
	if(x<0) return x+maxsz;
	return x;
  }
  val& operator[](key u){
	int hs=geths(u);
	for(int i=head[hs];i;i=e[i].next)if(e[i].u==u) return e[i].v;
	if(!head[hs])id[++numk]=hs;
	if(++nume>=e.size())e.resize(nume<<1);
	return e[nume]=(node){u,0,head[hs]},head[hs]=nume,e[nume].v;
  }
  void clear(){
	for(int i=0;i<=numk;i++) head[id[i]]=0;
	numk=nume=0;
  }
};
hash_map<PII,ll> ap;

ll nxt[N],vis[N];

void getNext(string s){
	nxt[0]=-1;
	for(int i=0,j=-1;i<s.length();){
		if(j==-1||s[i]==s[j]) nxt[++i]=++j;
		else j=nxt[j];
	}
}

string s[N];
int n;

int main(){
	init();
	n=input();
	for(int i=1;i<=n;i++) cin>>s[i];

	for(int i=1;i<=n;i++){
		str_hash(s[i]);
		PII tmp;
		for(int j=0;j<s[i].length();j++){
			tmp=gethash(j,s[i].length()-1);
			ap[tmp]++;
		}
	}

	ll Ans=0;
	for(int i=1;i<=n;i++){
		str_hash(s[i]);
		getNext(s[i]);

		for(int j=0;j<=s[i].length();j++) vis[j]=0;
		
		for(int j=s[i].length();j>=1;j--){
			PII tmp=gethash(0,j-1);

			Ans=(Ans+(ap[tmp]-vis[j]+mod[1])%mod[1]*j%mod[1]*j%mod[1])%mod[1];
			
			vis[nxt[j]]=(vis[nxt[j]]+ap[tmp])%mod[1];
		}
	}

	cout<<Ans<<endl;
}

B

简单计算几何。比赛的时候没有看到,赛后读完题就秒了。首先我们知道(0,0)一定在圆上,那么我们只需要枚举一个点,然后再枚举另一个点,计算有多少个圆心,找到被覆盖最多的圆心就是答案了。

#include <bits/stdc++.h>
 
using namespace std;
 
#define ll long long
ll input(){
    ll x=0,f=0;char ch=getchar();
    while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return f? -x:x;
}
 
#define PII pair <double,double>
#define fr first
#define sc second
#define mp make_pair
 
const int N=2007;
 
PII tcircle(PII A,PII B,PII C){
    double x1=A.fr,x2=B.fr,x3=C.fr;
    double y1=A.sc,y2=B.sc,y3=C.sc;
 
    double a = x1 - x2;
    double b = y1 - y2;
    double c = x1 - x3;
    double d = y1 - y3;
 
    double e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2))/2.0;
    double f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3))/2.0;
 
     double det = b * c - a * d;
     if(fabs(det)<1e-5){
        return mp(0,0);
     }
 
    double x0=-(d*e-b*f)/det;
    double y0=-(a*f-c*e)/det;
 
    return mp(x0,y0);
}
 
int n;
PII p[N];
vector <PII> v;
 
bool cmp(PII a,PII b){
    if(fabs(a.fr-b.fr)<1e-10&&fabs(a.sc-b.sc)<1e-10) return 1;
    else return 0;
}
 
int main(){
    n=input();
 
    int Ans=0;
 
    for(int i=1;i<=n;i++){
        p[i].fr=input(),p[i].sc=input();
    }
 
    for(int i=1;i<=n;i++){
        v.clear();
        for(int j=1;j<=n;j++){
            if(i==j) continue;
 
            PII tmp=tcircle(p[i],p[j],mp(0,0));
 
            if(tmp==mp(0.0,0.0)) continue;
            v.push_back(tmp);
        }
 	
 		if(v.empty()){printf("1\n");return 0;}
        sort(v.begin(),v.end());
 
        int cnt=1;
        for(int j=1;j<v.size();j++){
            if(cmp(v[j],v[j-1])) cnt++;
            else{
                Ans=max(Ans,cnt+1);
                cnt=1;
            }
        }
        Ans=max(Ans,cnt+1);
    }
 
    printf("%d\n",Ans);
}

J

置换群整数幂运算。题目要求对于一个置换\(T\)我们已知\(T^k\),我们要求\(T^1\)。我们可以构造\((T^k)^{inv}=T^{k*inv}=T^1\),所以我们现当于要求\(k\)的逆元\(inv\)\(mod\ len\)意义下。因此我们可以处理出每个置换中的环,然后计算所有的\(inv\),最后答案就是每个环位移每个环\(inv\)次。由于在一个置换上我们可以\(O(n)\)求出任意\(k\)次置换,所以本题总体复杂度是\(O(n)\)(具体理论参考《置换群快速幂运算 研究与探讨》(潘震皓))

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

const int N=1e5+7;

ll n,k;
int a[N],t[N],vis[N];
int Ans[N];
vector <int> cir;

void find(int x){
	vis[x]=1;
	cir.push_back(x);
	if(!vis[t[x]]) find(t[x]);
}

void cal(){
	int pos,len=cir.size();
	for(int i=0;i<len;i++){
		if(i*k%len==1){
			pos=i;
			break;
		}
	}

	for(int i=0;i<len;i++){
		Ans[cir[i]]=cir[(i+pos)%len];
	}
}

int main(){
	n=input(),k=input();

	for(int i=1;i<=n;i++){
		a[i]=t[i]=input();
	}

	for(int i=1;i<=n;i++){
		cir.clear();
		if(!vis[i]){
			find(i);
			cal();
		}
	}

	for(int i=1;i<=n;i++) printf("%d%c",Ans[i],i==n? '\n':' ');
}

H

细节巨多的数据结构题。对于一个三角形来说已知一条边x,我们不妨设另外两条边为\(a,b(a>b)\),那么\(a,b,x\)一定满足不等式\(a-b<x<a+b\),有\(x<a+b\)我们易知\(a\)一定大于\(\left \lfloor \frac{x}{2} \right \rfloor\),这样我们就确定了上界,要如何确定下界呢?我们观察\(a-b<x\)要使答案满足条件,那么\(a-b\)一定是在满足\(x<a+b\)时越小越好,另外一个一定满足条件的情况是当\(a>\left \lfloor \frac{x}{2} \right \rfloor\)时离\(a\)最近的数\(b\)一定是一个可行解。那么问题便很简单了,我们用线段树维护相邻两数的差,每次询问现当于询问区间\((\left \lfloor \frac{x}{2} \right \rfloor,10^9]\)的最小值是否小于\(x\)。用set动态维护\(x\)的前驱和后继,注意对边界条件进行讨论即可。

#include <bits/stdc++.h>

using namespace std;

#define ll long long
ll input(){
	ll x=0,f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f? -x:x;
}

#define ls(x) t[x].l
#define rs(x) t[x].r

const ll inf=0x3f3f3f3f3f3f3f;
const int N=4e5+7;

struct node{
	ll val;
	int l,r;
}t[N*40];
int n,root,cnt;

multiset <ll> S;
map <ll,int> mp;

void update(int &rt,int l,int r,int pos,ll x){
	if(!rt) rt=++cnt,t[rt].val=inf;
	if(l==r){
		t[rt].val=x;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) update(ls(rt),l,mid,pos,x);
	else update(rs(rt),mid+1,r,pos,x);
	t[rt].val=min(t[ls(rt)].val,t[rs(rt)].val);
}

ll query(int rt,int l,int r,int ql,int qr){
	if(!rt||r<l) return inf;
	if(ql<=l&&r<=qr) return t[rt].val;
	int mid=(l+r)>>1;
	if(mid<ql) return query(rs(rt),mid+1,r,ql,qr);
	if(qr<=mid) return query(ls(rt),l,mid,ql,qr);
	return min(query(ls(rt),l,mid,ql,qr),query(rs(rt),mid+1,r,ql,qr)); 
}

void add(int x){
	S.insert(x),mp[x]++;
	auto pre=S.lower_bound(x),nxt=S.upper_bound(x);
	if(mp[x]==1){
		if(pre==S.begin()) update(root,1,(int)1e9,x,inf);
		else{
			--pre;
			update(root,1,(int)1e9,x,x-*pre);
		}
	}else if(mp[x]==2) update(root,1,(int)1e9,x,0);

	if(nxt!=S.end()){
		if(mp[*nxt]==1){
			update(root,1,(int)1e9,*nxt,*nxt-x);
		}
	}
}

void del(int x){
	mp[x]--;
	S.erase(S.find(x));
	auto pre=S.lower_bound(x),nxt=S.upper_bound(x);
	if(mp[x]==1){
		if(pre!=S.begin()) --pre,update(root,1,(int)1e9,x,x-*pre);
		else update(root,1,(int)1e9,x,inf);
	}

	if(mp[x]==0){
		update(root,1,(int)1e9,x,inf);
		if(nxt!=S.end()){
			if(mp[*nxt]==1){
				if(pre!=S.begin()) --pre,update(root,1,(int)1e9,*nxt,*nxt-*pre);
				else update(root,1,(int)1e9,*nxt,inf);
			}
		}
	}
}

ll get(int x){
	auto it=S.lower_bound(x/2);
	if(it==S.end()) return 1e9;
	while(it!=S.end()){
		if(it!=S.begin()){
			int tmp=*it;
			--it;
			tmp+=*it;
			if(tmp>x){
				++it;
				return *it;
			}
			++it;
		}
		++it;
	}
	return 1e9;
}

int main(){
	t[0].val=inf;
	int q=input();

	for(int i=1;i<=q;i++){
		int op=input(),x=input();

		if(op==1){
			add(x);
		}else if(op==2){
			del(x);
		}else{
			if(query(root,1,(int)1e9,get(x),(int)1e9)<x) printf("Yes\n");
			else printf("No\n");
		}
	}
}
posted @ 2020-07-13 23:03  _aether  阅读(369)  评论(1编辑  收藏  举报