CCPC-Wannafly & Comet OJ 夏季欢乐赛(2019)

感觉自己代码能力好弱啊


T1 完全k叉树

传送门


Solution

首先特判一下 K = 1 K=1 K=1
然后处理出最大满 K K K叉树,设这棵树的深度为 r a n k rank rank,根节点的深度为 0 0 0,这个时候的答案,就是 2 ∗ r a n k 2*rank 2rank
如果还有剩下的节点,显然如果答案可以扩大为 2 ∗ r a n k + 1 2*rank+1 2rank+1
现在已经考虑了深度小于等于 r a n k rank rank的点互相匹配 and 深度为 r a n k + 1 rank+1 rank+1与深度小于等于 r a n k rank rank的点配对的情况。
还有一种情况就是深度为 r a n k + 1 rank+1 rank+1的点相配对,如果要使答案变大,则匹配的两个点的 l c a lca lca必须是根节点(太好证了,请自行证明),处理出根节点的最左边儿子在 r a n k + 1 rank+1 rank+1层需要多少个节点才能填满,与剩下的节点判断一下大小就好了。

#include<iostream>
using namespace std;
typedef long long ll;
ll getSize(ll K,ll rk){
	ll ret = 1;
	ll cur = 1;
	for(int i=1;i<=rk;i++){
		cur *= K;
		ret += cur;
	}
	return cur;
}
void solve(){
	ll K,N;
	cin>>K>>N;
	if(K==1){
		cout<<N-1<<endl;
		return;
	}
	ll cur = 1;
	ll rank = 0;
	ll tmp = N-1;
	while(tmp >= cur * K){
		rank++;
		tmp -= cur * K;
		cur *= K;
	}
	if(rank==0){
		if(N==2){cout<<1<<endl;return;}
		else{cout<<2<<endl;return;}
	}
	ll ans = (rank<<1);
	if(tmp == 0){
		cout<<ans<<endl;
		return;
	}
	ans = max(rank*2+1,ans);
	ll pre = tmp / K;
	if(tmp % K)pre++;
	ll ned = getSize(K,rank-1);
	if(pre > ned){
		ans = max(rank*2+2,ans);
	}
	cout<<ans<<endl;
	
}
int main(){
	ios::sync_with_stdio(0);
	int T;
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}

T2 距离产生美

传送门


Solution

发现最大可以把数字修改成 1 0 18 10^{18} 1018 K K K最大才 1 0 9 10^9 109
也就是说,修改过一个数之后,这个数的前后两个数与当前这个数肯定能满足题意
就可以 d p dp dp
f [ i ] [ 0 ] f[i][0] f[i][0]为前 i i i个,第 i i i个没修改, f [ i ] [ 1 ] f[i][1] f[i][1],为前 i i i个,第 i i i个修改了的最小值
转移,直接看代码

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 200000;
int n,k,data[MAXN],f[MAXN][2];
int Abs(int x){
	return x>0?x:-x;
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++)
		cin>>data[i];
	for(int i=1;i<=n;i++){
		if(i==1){
			f[i][1]=1;
		}else{
			if(Abs(data[i]-data[i-1])>=k){
				f[i][0]=min(f[i-1][0],f[i-1][1]);
				f[i][1]=min(f[i-1][0],f[i-1][1])+1;
			}else{
				f[i][0]=f[i-1][1];
				f[i][1]=min(f[i-1][0],f[i-1][1])+1;
			}
		}
	}
	cout<<min(f[n][0],f[n][1]);
	return 0;
}

T3 烤面包片

传送门


Solution

注意特判 N = 0 N=0 N=0 m o d = 0 mod=0 mod=0的时候
剩下的可以发现 n n n在很小的时候 n ! ! ! n!!! n!!!就比 1 0 9 10^9 109大了
就可以暴力了
细节看代码

#include<iostream>
using namespace std;
typedef long long ll;
ll n,mod;
ll ksc(ll a,ll b,ll p){
	ll ret = 0;
	while(b){
		if(b&1){ret = (ret+a)%p;}
		a = (a*2)%p;
		b>>=1;
	}
	return ret;
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>mod;
	
	if(mod==1){cout<<0;return 0;}
	if(n==0){
			cout<<1;
			return 0;
		}
	ll cur = 1;
	for(ll i=n;i>=1;i--){
		double  tmp = cur;
		tmp *= i;
		cur *= i;
		if(tmp>=mod){
			cout<<0;
			return 0;
		}
	}
	n = cur;
	cur = 1;
	for(ll i=n;i>=1;i--){
		double tmp = cur;
		tmp *= i;
		cur *= i;
		if(tmp>=mod){
			cout<<0;
			return 0;
		}
	}
		n = cur;
	cur = 1;
	for(ll i=n;i>=1;i--){
		cur = ksc(cur,i,mod);
	}
	cout<<cur;
	return 0;
}

T4 茶颜悦色

传送门


Solution

y y y y − k y-k yk离散化
然后对 x x x排序用扫描线
对于一个点可以发现它对底边在 [ y − k , y ] [y-k,y] [yk,y]的正方形有贡献
用线段树维护贡献值,相当于区间加减和查询全局最大值

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5+10;
int n,k;
struct _{
	int x,y;
};
_ data[MAXN];
int buf[MAXN],tot,tr[MAXN<<3],tag[MAXN<<3];
void pushup(int x){tr[x]=max(tr[x<<1],tr[x<<1|1]);}
void pushdown(int x){
	tag[x<<1]+=tag[x];tag[x<<1|1]+=tag[x];
	tr[x<<1] += tag[x]; tr[x<<1|1] += tag[x]; 	tag[x]=0;
}
void Modify(int rt,int l,int r,int L,int R,int delta){
	pushdown(rt);int mid=(l+r)>>1;
	if(l==L&&r==R){tag[rt]=delta;tr[rt]+=delta;return;}
	if(R<=mid)Modify(rt<<1,l,mid,L,R,delta);else if(L>mid)Modify(rt<<1|1,mid+1,r,L,R,delta);
	else{Modify(rt<<1,l,mid,L,mid,delta);Modify(rt<<1|1,mid+1,r,mid+1,R,delta);}
	pushup(rt);
}
bool cmp(_ a,_ b){return a.x<b.x;}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>data[i].x>>data[i].y;
		buf[++tot]=data[i].y;
		buf[++tot]=data[i].y-k;
	}
	sort(buf+1,buf+1+tot);
	tot=unique(buf+1,buf+1+tot)-buf-1;
	sort(data+1,data+1+n,cmp);
	int l=1,ans=0;
	for(int i=1;i<=n;i++){
		int L=lower_bound(buf+1,buf+1+tot,data[i].y-k)-buf,R=lower_bound(buf+1,buf+1+tot,data[i].y)-buf;
		while(data[l].x+k<data[i].x){
			Modify(1,1,tot,lower_bound(buf+1,buf+1+tot,data[l].y-k)-buf,lower_bound(buf+1,buf+1+tot,data[l].y)-buf,-1);
			l++;
		}
		Modify(1,1,tot,L,R,1);
		ans=max(ans,tr[1]);
	}
	cout<<ans;
	return 0;
}

T5 飞行棋

传送门


Solution

正着不好做就倒着做,设起点为 1 1 1,终点为 d + 1 d+1 d+1
f [ i ] f[i] f[i]为从终点走到点 i i i的期望步数,则 f [ 1 ] f[1] f[1]就是最后的答案
因为最后的 K + 1 K+1 K+1个节点非常烦,可以直接高斯消元求出来(我求出来才发现有规律……)
然后对于一个不在最后 K + 1 K+1 K+1个节点之内的点

∑ j &gt; i 且 i + k &gt; = j f [ j ] / K + 1 = f [ i ] \sum_{j&gt;i且i+k&gt;=j}{f[j]/K}+1=f[i] j>ii+k>=jf[j]/K+1=f[i]
就可以矩阵快速幂了,那个 + 1 +1 +1可以单独开一维保存常数
细节看代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXK = 25;
const int MOD=1e9+7;
ll Exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){x=1;y=0;return a;}
	ll d=Exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}
ll GetInv(ll x){
	ll a,b;
	Exgcd(x,MOD,a,b);
	return (a+MOD)%MOD;
}
void update(ll &x){
	x%=MOD;
	x=(x+MOD)%MOD;
}
ll k,d,dishu;
ll data[MAXK][MAXK];
struct Mar{
	ll a[MAXK][MAXK];
	Mar(){
		memset(a,0,sizeof a);
	}
	void clear(){
		memset(a,0,sizeof a);
	}
};
Mar operator * (Mar x,Mar y){
	Mar c;
	for(int i=1;i<=k;i++){
		for(int j=1;j<=k;j++){
			for(int h=1;h<=k;h++){
				c.a[i][j]=(c.a[i][j]+(x.a[i][h]*y.a[h][j])%MOD)%MOD;
			}
		}
	}
	return c;
}
void Gauss(){
	ll n = k+1;
	data[n][n]=1;
	for(int i=1;i<n;i++){
		for(int j=1;j<=k;j++){
			int toward = i+j;
			if(toward > n){
				toward = n - (toward - n);
			}
			data[i][toward] += dishu;
			data[i][n+1] -= dishu;
			update(data[i][toward]);
			update(data[i][n+1]);
		}
		data[i][i]--;
		update(data[i][i]);
	}	
	for(int i=1;i<=n;i++){
		if(data[i][i]==0)continue;
		ll inv = GetInv(data[i][i]);
		for(int j=1;j<=n+1;j++){
			data[i][j]=(data[i][j]*inv)%MOD;
		}
		for(int j=1;j<=n;j++){
			if(data[j][i]==0 || i==j)continue;
			ll D = data[j][i];
			for(int h=1;h<=n+1;h++){
				data[j][h] = (data[j][h] - data[i][h]*D)%MOD;
				update(data[j][h]);
			}
		}
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin>>d>>k;
	dishu = GetInv(k);
	Gauss();
	if(d==k){
		cout<<data[1][k+2];
		return 0;
	}

//	k++;	
	Mar Init; Init.clear();
	for(int i=k;i>=1;i--){
		Init.a[1][k-i+1] = data[i][k+2];
	}
	k++;
	Init.a[1][k] = 1;
	d -= (k-1);
	Mar Trans; Trans.clear();
	for(int i=1;i<k-1;i++){
		Trans.a[i+1][i]=1;
	}
	for(int i=1;i<k;i++){
		Trans.a[i][k-1]=dishu;
	}
	Trans.a[k][k-1]=1;
	Trans.a[k][k]=1;
	Mar I; I.clear();
	for(int i=1;i<=k;i++)I.a[i][i]=1;
	while(d){
		if(d&1){
			I=I*Trans;
		}
		Trans=Trans*Trans;
		d>>=1;
	}
	Init = Init * I;
	cout<<Init.a[1][k-1];
	return 0;
}

T6 三元组

传送门


Solution

发现那个不等式只可能有两种成立方式
2 ( a i + a j ) &lt; = b i + b j 2(a_i+a_j)&lt;=b_i+b_j 2(ai+aj)<=bi+bj
2 ( b i + b j ) &lt; = a i + a j 2(b_i+b_j)&lt;=a_i+a_j 2(bi+bj)<=ai+aj
这两种方式是不可能同时成立的(证明很显然,不证了)
可以分别统计
把第一个式子分离一下有
2 a i − b i &lt; = b j − 2 a j 2a_i-b_i&lt;=b_j-2a_j 2aibi<=bj2aj
显然可以分治或者离散化一下随便找个数据结构维护
第二种情况把 a , b a,b a,b互换再做一次就好了
我不是知道我那根神经抽了,写的分治

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
const int MOD = 1e9+7;
const int MAXN = 1e5+2;
typedef long long ll;
struct _{
	ll a,b,c,v1,v2;
	int loc;
};
_ data[MAXN],data2[MAXN];
int n;
void Build(){
	rep(i,1,n){
		data[i].v1=2*data[i].a-data[i].b;
		data[i].v2=-data[i].v1;
	}
}
void Read(){
	cin>>n;
	rep(i,1,n){
		cin>>data[i].a>>data[i].b>>data[i].c;
		data[i].loc=i;
	}
}
_ buf[MAXN];
ll ans;
void DAC(int l,int r){
	int mid=(l+r)/2;
	if(l==r){
		if(data[l].v1<=data[l].v2){
			ans = (ans + (data[l].c*data[l].c)%MOD)%MOD;
		}
		return;
	}
	DAC(l,mid);
	DAC(mid+1,r);
	int R = r;
	ll cur = 0;
	for(int i=mid;i>=l;i--){
		while(R>=mid+1 && data[i].v1 <= data2[R].v2){
			cur = (cur + data2[R].c)%MOD;
			R--;
		}
			ans = (ans + (cur * data[i].c)%MOD)%MOD;
	}
	int p1=l,p2=mid+1,tot=l;
	while(p1<=mid||p2<=r){
		if(p1<=mid&&p2<=r){
			if(data[p1].v1 < data[p2].v1){
				buf[tot++]=data[p1++];	
			}else{
				buf[tot++]=data[p2++];
			}
		}else if(p1<=mid){
			buf[tot++]=data[p1++];
		}else{
			buf[tot++]=data[p2++];
		}
	}
	rep(i,l,r)data[i]=buf[i];

	p1=l;p2=mid+1;tot=l;
	while(p1<=mid||p2<=r){
		if(p1<=mid&&p2<=r){
			if(data2[p1].v2 < data2[p2].v2){
				buf[tot++]=data2[p1++];
			}else{
				buf[tot++]=data2[p2++];
			}
		}else if(p1<=mid){
			buf[tot++]=data2[p1++];
		}else{
			buf[tot++]=data2[p2++];
		}
	}
	rep(i,l,r)data2[i]=buf[i];
}	
bool cmp(_ x,_ y){
	return x.loc < y.loc;
}
int main(){
	ios::sync_with_stdio(0);
	Read();
	Build();
	rep(i,1,n)data2[i]=data[i];
	DAC(1,n);
	sort(data+1,data+1+n,cmp);
	rep(i,1,n)swap(data[i].a,data[i].b);
	Build();
	rep(i,1,n)data2[i]=data[i];
	DAC(1,n);
	cout<<ans;
	return 0;
}

T7 篮球校赛

传送门


Solution

状压 d p dp dp板子题,直接上代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+2;
ll f[MAXN][1<<5];
ll n,data[MAXN][6];
int main(){
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=5;j++){
			cin>>data[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int s=1;s<(1<<5);s++){
			f[i][s] = -0x3f3f3f3f3f3f3f3fLL;
		}
	}
	for(int i=1;i<=n;i++){
		for(int s=1;s<(1<<5);s++){
			f[i][s] = max(f[i][s],f[i-1][s]);
			for(int j=0;j<5;j++){
				if(s&(1<<j)){
					f[i][s] = max(f[i-1][s^(1<<j)]+data[i][j+1],max(f[i][s],f[i-1][s]));
				}
			}
		}
	}
	cout<<f[n][(1<<5)-1];
	return 0;
}

T8 分配学号

传送门


Solution

可能是我计数能力太弱了,我感觉这是最难的一道题。
首先最后的完成的序列排序后一定是由一段又一段连续的数构成的,不然没法保证次数最小。
所以排序后从小到大贪心。
如果能构成一段连续的数那就不管,如果不行就要开始改变学号。
现在只考虑一段需要修改成一段连续的数的序列
比如 1 , 1 , 1 , 1 1,1,1,1 1,1,1,1
又贪心
最后一定是
1 , 2 , 3 , 4 1,2,3,4 1,2,3,4
总的代价等于最后的代数和-最初的代数和,所以只需要给最初的序列进行排列,然后依次赋值就行了。
可是题目还有一个条件是,修改后的学号一定要变大。

那么对于这样一段序列,我们对每个数减去(第一个数-1),化成以数值1开头的序列 a [ i ] a[i] a[i]
这样问题就变成了
问有多少个排列 b [ i ] b[i] b[i],使得一个数 j j j b [ i ] b[i] b[i]中的位置大于等于 a [ j ] a[j] a[j]
用插空法计数,设 f [ i ] f[i] f[i]为前 i i i个数的答案
f [ i ] = f [ i − 1 ] ∗ ( i − a [ i ] + 1 ) f[i] = f[i-1]*(i-a[i]+1) f[i]=f[i1](ia[i]+1)
意思是第 i i i个数最靠前可以插在原来第 a [ i ] a[i] a[i]个数的前面,让它的排名至少为 a [ i ] a[i] a[i]
然后一共有 i i i个位置。
说了这么多,代码却那么短

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int MAXN = 1e5+2;
int n;
ll data[MAXN];
int main(){
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;i++){cin>>data[i];}
	sort(data+1,data+1+n);
	ll ans = 1;
	for(int j,i=1;i<=n;i=j){
		for(j=i+1;j<=n&&data[j]<data[i]+j-i;j++){
			ans = ans * (data[i]+j-i-data[j]+1) % MOD;
		}	
	}
	cout<<ans;
	return 0;
}

T9 Gree的心房

传送门


Solution

很容易发现答案的下界是 n + m − 2 n+m-2 n+m2,走最上面一行和最右边一列。
然后障碍物最多有 ( n − 1 ) ∗ ( m − 1 ) (n-1)*(m-1) (n1)(m1)个。

#include<iostream>
using namespace std;
typedef long long ll;
ll n,m,k;
int main(){
	cin>>n>>m>>k;
	if(k>((n-1)*(m-1))){cout<<-1;}
	else{cout<<n-1+m-1;}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值