已补 4 / 5 4/5 4/5
CodeForces 1268C
题意
对于
n
n
n的排列,
f
(
i
)
f(i)
f(i)表示求出通过交换相邻元素得到
1
1
1~
i
i
i的连续段的最小次数。
并输出
f
(
i
)
,
i
∈
[
1
,
n
]
f(i),i∈[1,n]
f(i),i∈[1,n]
题解
如果是对于连续段相互交换得到最终阶段,显然答案是逆序对个数。
而这个实际情况是要把非连续转换成连续。
要求最小化
∣
p
o
s
1
−
(
x
−
k
)
∣
+
∣
p
o
s
2
−
(
x
−
k
+
1
)
∣
+
.
.
.
+
∣
p
o
s
k
+
1
−
x
∣
+
.
.
.
+
∣
x
+
n
−
(
k
+
1
)
−
p
o
s
i
∣
|pos_1-(x-k)|+|pos_2-(x-k+1)|+...+|pos_{k+1}-x|+...+|x+n-(k+1)-pos_i|
∣pos1−(x−k)∣+∣pos2−(x−k+1)∣+...+∣posk+1−x∣+...+∣x+n−(k+1)−posi∣
显然要挑选中位数。
根据上面的式子,可以维护
p
o
s
pos
pos的和,判断和中位数的大小差。
而逆序对,因为每次插入的都是最大的,所以只用考虑插入位置过后已经插入多少个了。
这些都是可以在一个线段树里维护的。
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
const int maxn = 500050;
typedef long long ll;
struct tree2{
tree2 *lson,*rson;
ll x,cnt;
}dizhi[maxn<<2],*root=&dizhi[0];
int n,m,t=1,pos[maxn];
ll pre[maxn];
void push_up(tree2 *tree,int l,int r){
tree->x=tree->lson->x+tree->rson->x;
tree->cnt=tree->lson->cnt+tree->rson->cnt;
}
void build(tree2 *tree,int l,int r){
tree->x=tree->cnt=0;
if(l==r)return ;
tree->lson=&dizhi[t++];
tree->rson=&dizhi[t++];
int mid=(l+r)>>1;
build(tree->lson,l,mid);
build(tree->rson,mid+1,r);
push_up(tree,l,r);
}
void update(tree2 *tree,int l,int r,int pos){
if(l==r){
// cout<<"?"<<l<<endl;
tree->cnt=1;
tree->x=pos;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid)update(tree->lson,l,mid,pos);
else update(tree->rson,mid+1,r,pos);
push_up(tree,l,r);
}
ll query_pos(tree2 *tree,int l,int r,int x,int y){
if(x<=l&&r<=y)return tree->x;
int mid=(l+r)>>1;
ll t1=0,t2=0;
if(x<=mid)t1=query_pos(tree->lson,l,mid,x,y);
if(y>mid)t2=query_pos(tree->rson,mid+1,r,x,y);
return t1+t2;
}
int query_num(tree2 *tree,int l,int r,int x,int y){
if(x<=l&&r<=y)return tree->cnt;
int mid=(l+r)>>1;
int t1=0,t2=0;
if(x<=mid)t1=query_num(tree->lson,l,mid,x,y);
if(y>mid)t2=query_num(tree->rson,mid+1,r,x,y);
return t1+t2;
}
int query(tree2 *tree,int l,int r,int k){
int mid=(l+r)>>1;
if(l==r)return l;
if(tree->lson->cnt>k)return query(tree->lson,l,mid,k);
else return query(tree->rson,mid+1,r,k-(tree->lson->cnt));
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
pos[x]=i;
}
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+1ll*i;
build(root,1,n);
ll now=0;
for(int i=1;i<=n;i++){
int p=pos[i];
now+=1ll*query_num(root,1,n,p,n);//逆序对个数
update(root,1,n,p);//更新
int mid=query(root,1,n,i/2);;
ll l=0,r=0;
if(1<=mid-1)l=query_pos(root,1,n,1,mid-1);
if(mid<=n)r=query_pos(root,1,n,mid,n);
ll ans=1ll*mid*(i/2)-l+r-1ll*mid*(i-i/2);
ans+=now;
ans+=-pre[i/2]-pre[i-i/2-1];
printf("%lld ",ans);
}
puts("");
}
CodeForces 1271E
题意
对于当
x
%
2
=
=
1
,
x\%2==1,
x%2==1,
f
(
x
)
=
x
−
1
f(x)=x-1
f(x)=x−1;当
x
%
2
=
=
0
,
f
(
x
)
=
x
2
x\%2==0,f(x)=\frac{x}{2}
x%2==0,f(x)=2x.
对于一个
x
x
x,可以得到这样的序列直到等于
1
1
1。得到序列为
{
x
,
x
2
,
.
.
.
,
.
.
.
}
\{x,\frac{x}{2},...,...\}
{x,2x,...,...}
对于每个
i
i
i都有这样的序列,求出现次数大于等于
k
k
k的最大数。
题解
我们可以考虑每个数被多少个包括。
对于奇数:
x
、
2
x
、
2
x
+
1
、
4
x
、
4
x
+
1
、
4
x
+
2
、
4
x
+
3
、
.
.
.
x、2x、2x+1、4x、4x+1、4x+2、4x+3、...
x、2x、2x+1、4x、4x+1、4x+2、4x+3、...
对于偶数:
x
、
x
+
1
、
2
x
、
2
x
+
1
、
2
x
+
2
、
2
x
+
3
、
.
.
.
x、x+1、2x、2x+1、2x+2、2x+3、...
x、x+1、2x、2x+1、2x+2、2x+3、...
显然都是满足
1
、
2
、
4
、
8
1、2、4、8
1、2、4、8的。
最大值分别是
2
k
x
+
2
k
−
1
2^kx+2^k-1
2kx+2k−1和
2
k
x
+
2
k
+
1
−
1
2^kx+2^{k+1}-1
2kx+2k+1−1
第二个看着不明显,建议在往下推一个就很明了了。
显然这就是出现次比如数了,显然当出现次数刚好大于等于
k
k
k了,这个值肯定是最大的。
分别考虑奇偶,去计算出答案。
对于上述情况,
x
=
1
x=1
x=1是不行的,所以需要特判(因为
x
+
1
=
2
x
x+1=2x
x+1=2x)
推出来公式即可(保证最大值
−
-
− (前缀和
−
k
- k
−k )
≤
n
\leq n
≤n)
这样保证刚好最大,但不一定能保证奇偶性,所以需要特判。
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
const int maxn = 100+1;
const int up=61;
typedef long long ll;
ll odd[maxn],even[maxn];
ll sumodd[maxn],sumeven[maxn];
ll k,n;
ll sloveodd(){
int x=1;
for(x=1;x<=up;x++){
if(sumodd[x]>=k)break;
}
ll ret=n+(sumodd[x]-k)-(odd[x]-1);
ll t=ret/odd[x];
if(t%2)return t;
else{
if((t+1)*odd[x]<=ret)return t+1;
else if(t-1>=1)return t-1;
}
}
ll sloveeven(){
int x;
for(x=1;x<=up;x++){
if(sumeven[x]>=k)break;
}
ll ret=n+(sumeven[x]-k)-(even[x]-1);
ll t=ret/(even[x]/2);
if(t%2==0)return t;
else{
if((t+1)*(even[x]/2)<=ret&&t+1<=n)return t+1;
else if(t-1>=1)return t-1;
}
}
int main(){
odd[1]=1;sumodd[1]=1;
for(int i=2;i<=up;i++)odd[i]=odd[i-1]*2,sumodd[i]=sumodd[i-1]+odd[i];
even[1]=2;sumeven[1]=2;
for(int i=2;i<=up;i++)even[i]=even[i-1]*2,sumeven[i]=sumeven[i-1]+even[i];
cin>>n>>k;
if(k==n)puts("1");
else{
ll ans1=sloveodd();
ll ans2=sloveeven();
ll ans=max(ans1,ans2);
cout<<ans<<endl;
}
}
CodeForces 1280C
题意
一棵树,树上分配 2 k 2k 2k个人,每个人和另一个人有关系,且不会同时和别人有关系。问有关系的一对人之间的距离和最大最小为多少
题解
考虑每条边跑多少次。比如边
x
x
x,要求最大的话,就是尽量平均分配。
要求最小的话,就是尽量都放一边。
d
f
s
dfs
dfs跑
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200050;
typedef long long ll;
vector<pair<ll,int> >G[maxn];
int sz[maxn];
int t,k;
ll lans,rans;
void dfs(int u,int f){
sz[u]=1;
for(auto it:G[u]){
int v=it.second;
ll val=it.first;
if(v==f)continue;
dfs(v,u);
sz[u]+=sz[v];
lans+=1ll*min(2*k-sz[v],sz[v])*val;
if(sz[v]%2&&(2*k-sz[v])%2)rans+=val;
}
}
int main(){
cin>>t;
while(t--){
scanf("%d",&k);
for(int i=1;i<=2*k;i++)G[i].clear(),sz[i]=0;
for(int i=1;i<=2*k-1;i++){
int u,v;ll val;scanf("%d%d%lld",&u,&v,&val);
G[u].push_back(make_pair(val,v));
G[v].push_back(make_pair(val,u));
}
lans=0,rans=0;
dfs(1,-1);
printf("%lld %lld\n",rans,lans);
}
}
CodeForces 514D
忘了…但是很明显就是五个线段树。卡卡常