心得
有的时候,的确是缺乏休息吧,赛前两天好好休息休息
本来觉得挺难写的,睡了一觉发现这都是什么sb题……
C - The Football Season(枚举/exgcd)
1<=n<=1e12,0<=p<=1e17,1<=d<w<=1e5
输出任一组非负三元组解(x,y,z)满足x⋅w+y⋅d=p且x+y+z=n,无解输出-1
显然x+y越小越好,w>d时贪心x越大越好y越小越好,
取y非负最小解即可,扩欧姿势不对会炸ll,
要先约一下w和d的gcd,求y*d=1(mod w)的逆元y
把p模过w之后再乘y,能模就模,
但注意到,y的解不会超过w(否则可以不断执行y-=w,x+=d),枚举y更新答案不会炸
D - Paint the Tree(线性dp)
用0 1 2三种颜色染n(n<=1e)节点的树,第i个染法对于第j个节点有代价c[i][j]
树上相邻三元组(x,y,z)(即x与y邻接,y与z邻接)颜色不同条件下,输出最小代价和
自己dp太麻烦,看了下别人的代码,茅塞顿开
显然度>=3无解,否则就是一条链,dfs处理塞进vector,
根本不用dp[N][6]六种状态或dp[N][3][3]枚举这一位填什么上一位填什么转移最后倒着扫一遍dp序列反向求路径
对于链序列,答案只可能是
0 1 2 0 1 2 ... 或 0 2 1 0 2 1 ... 或 1 0 2 1 0 2 ...
共6种,所以暴力跑下全排列,能更新就记录下路径
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+10;
bool ok;
int n,num[3],rt,c[3][N],u,v;
vector<int>E[N],f;
int ans[N];
void dfs(int x,int fa)
{
f.pb(x);
for(int v:E[x])
{
if(v==fa)continue;
dfs(v,x);
}
}
ll solve(int x)
{
f.pb(0);
dfs(x,-1);
for(int i=0;i<3;++i)
num[i]=i;
ll res=8e18;
do
{
ll tmp=0;
for(int i=1;i<=n;++i)
tmp+=c[num[i%3]][f[i]];
if(res>tmp)
{
res=tmp;
for(int i=1;i<=n;++i)
ans[f[i]]=num[i%3];
}
}while(next_permutation(num,num+3));
return res;
}
int main()
{
scanf("%d",&n);
for(int j=0;j<3;++j)
{
for(int i=1;i<=n;++i)
{
scanf("%d",&c[j][i]);
}
}
for(int i=1;i<n;++i)
{
scanf("%d%d",&u,&v);
E[u].pb(v),E[v].pb(u);
}
for(int i=1;i<=n;++i)
{
if(E[i].size()>=3)
{
ok=1;
break;
}
if(E[i].size()==1)rt=i;
}
if(ok)puts("-1");
else
{
printf("%I64d\n",solve(rt));
for(int i=1;i<=n;++i)
printf("%d%c",1+ans[i]," \n"[i==n]);
}
return 0;
}
E. Minimizing Difference(枚举二分/贪心双指针)
n(2<=n<=1e5)个数,第i个数ai(1<=ai<=1e9),
有k(1<=k<=1e14)次机会,用一次机会可以将一个数+1或-1
求合理使用后,最大数mx-最小数mn的值最小是多少
题解做法,枚举+二分,
考虑最后结果[mn,mx]如果一个端点和原序列不重合,
那么说明此时把所有等于mn的数+1/-1和把所有等于mx的数+1/-1的代价相同,
因此,总可以左右滑动这个区间,使得这个区间和一个端点重合
枚举最大值up,先把大于up的都调到up,再二分找到最小值下界down,
判一个二分的最小值mid是否合法时,套二分找到小于下界的最大位置pos,
[1,pos]内数之和应被调至pos*mid,前缀和维护下子段和
再用相同方法做一遍枚举最小值,复杂度O(n*logn*logn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,k,a[N],sum[N],ans,pos;
ll up,down,l,r,L,R,mid,nowk;
int main()
{
scanf("%I64d%I64d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%I64d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;++i)
sum[i]=sum[i-1]+a[i];
ans=8e18;
for(int i=n;i>=1;--i)
{
up=a[i];
l=a[1],r=a[i];
nowk=k-((sum[n]-sum[i])-(n-i)*up);
if(nowk<0)continue;
while(l<=r)
{
mid=(l+r)/2;
pos=lower_bound(a+1,a+i+1,mid)-a;
if(a[pos]>mid)pos--;
if(pos*mid-sum[pos]<=nowk)l=mid+1;
else r=mid-1;
}
ans=min(ans,up-r);
}
for(int i=1;i<=n;++i)
{
down=a[i];
l=a[i],r=a[n];
nowk=k-(i*down-sum[i]);
if(nowk<0)continue;
while(l<=r)
{
mid=(l+r)/2;
pos=lower_bound(a+i+1,a+n+1,mid)-a;
if(sum[n]-sum[pos-1]-(n-pos+1)*mid<=nowk)r=mid-1;
else l=mid+1;
}
ans=min(ans,l-down);
}
printf("%I64d\n",ans);
return 0;
}
考虑贪心+双指针O(n)做法,不断将小值向上拔,大值向下压,
如果当前有i个值相等为mn,j个值相等为mx,
显然,i<=j时拔mn,i>j时降mx,
个数变化,仅发生在和序列中下一个数合并时,i++/j++
因此,每次判断当前机会数k是否足以与下一个数合并,
不能时,说明不足以跳到下一个数,跳到中间之后break即可
由于要排个序,最后还是O(nlogn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,k,a[N];
ll up,down,l,r;
ll pre,suf;
int main()
{
scanf("%I64d%I64d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%I64d",&a[i]);
sort(a+1,a+n+1);
l=1,r=n;
down=a[1];up=a[n];
while(l<r)
{
while(l<r&&a[l+1]==a[l])l++;
while(r>l&&a[r-1]==a[r])r--;
pre=a[l+1]-a[l];
suf=a[r]-a[r-1];
if(l<=n-r+1)
{
if(l*pre<k)
{
k-=l*pre;
l++;
down=max(down,a[l]);
}
else
{
down+=k/l;
break;
}
}
else
{
if((n-r+1)*suf<k)
{
k-=(n-r+1)*suf;
r--;
up=min(up,a[r]);
}
else
{
up-=k/(n-r+1);
break;
}
}
}
printf("%I64d\n",up-down);
return 0;
}