感觉自己代码能力好弱啊
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
2∗rank
如果还有剩下的节点,显然如果答案可以扩大为
2
∗
r
a
n
k
+
1
2*rank+1
2∗rank+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
y−k离散化
然后对
x
x
x排序用扫描线
对于一个点可以发现它对底边在
[
y
−
k
,
y
]
[y-k,y]
[y−k,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
>
i
且
i
+
k
>
=
j
f
[
j
]
/
K
+
1
=
f
[
i
]
\sum_{j>i且i+k>=j}{f[j]/K}+1=f[i]
j>i且i+k>=j∑f[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
)
<
=
b
i
+
b
j
2(a_i+a_j)<=b_i+b_j
2(ai+aj)<=bi+bj
2
(
b
i
+
b
j
)
<
=
a
i
+
a
j
2(b_i+b_j)<=a_i+a_j
2(bi+bj)<=ai+aj
这两种方式是不可能同时成立的(证明很显然,不证了)
可以分别统计
把第一个式子分离一下有
2
a
i
−
b
i
<
=
b
j
−
2
a
j
2a_i-b_i<=b_j-2a_j
2ai−bi<=bj−2aj
显然可以分治或者离散化一下随便找个数据结构维护
第二种情况把
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[i−1]∗(i−a[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+m−2,走最上面一行和最右边一列。
然后障碍物最多有
(
n
−
1
)
∗
(
m
−
1
)
(n-1)*(m-1)
(n−1)∗(m−1)个。
#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;
}