题解&反思
没做过原题*1,用的不是大部分做法
首先贪心是每次都刷最长能刷段,所以先造一个取min的st表,然后用一个vector存一下每个值的位置,每到一段就查一下最小值,然后再这个值的vector里二分一下要断的区间,递归处理即可
这样比每次扫区间的在极限数据上要快一些……直接分治是可以卡成1e8的
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=100005;
int n,a[N],st[20][N],bt[N];
long long ans;
vector<int>b[10005];
int read()
{
int r=0,f=1;
char p=getchar();
while(p>'9'||p<'0')
{
if(p=='-')
f=-1;
p=getchar();
}
while(p>='0'&&p<='9')
{
r=r*10+p-48;
p=getchar();
}
return r*f;
}
int ques(int l,int r)
{
int k=bt[r-l+1];
return min(st[k][l],st[k][r-(1<<k)+1]);
}
int ef1(int w,int v)
{
int l=0,r=b[w].size()-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(b[w][mid]>=v)
r=mid-1,ans=mid;
else
l=mid+1;
}
return ans;
}
int ef2(int w,int v)
{
int l=0,r=b[w].size()-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(b[w][mid]<=v)
l=mid+1,ans=mid;
else
r=mid-1;
}
return ans;
}
void wk(int l,int r,int v)
{
if(l>r)
return;
int mn=ques(l,r);
ans+=mn-v;
if(l==r)
return;
int ll=ef1(mn,l),rr=ef2(mn,r);
wk(l,b[mn][ll]-1,mn);
for(int i=ll+1;i<=rr;i++)
wk(b[mn][i-1]+1,b[mn][i]-1,mn);
wk(b[mn][rr]+1,r,mn);
}
void clc(int l,int r,int v)
{
int nw=1e9;
for(int i=l;i<=r;i++)
nw=min(nw,a[i]-v);
ans+=nw;
if(l==r)
return;
for(int i=l,j=l;i<=r;i=j+1)
{
j=i;
if(a[j]-v>nw)
{
while(j<r&&a[j+1]-v>nw)
j++;
clc(i,j,v+nw);
}
}
}
int main()
{
n=read();
bt[0]=-1;
for(int i=1;i<=n;i++)
a[i]=st[0][i]=read(),bt[i]=bt[i>>1]+1,b[a[i]].push_back(i);
for(int i=1;i<=17;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
if(n>1000)
wk(1,n,0);
else
clc(1,n,0);
printf("%lld\n",ans);
return 0;
}
题解&反思
没做过原题*2
贪心的考虑答案即可一定是原来集合的子集,然后去掉的就是能被别的数表示的数
先sort一下,然后注意到ai≤25000,所以就直接用v[i]表示i这个数能不能被表示,从小到大,用不能表示数更新v[x]:(v[x-a[i]]==1||x%a[i]==0)即可(其实就是背包)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=105;
int T,n,a[N],ans;
bool v[25005];
int read()
{
int r=0,f=1;
char p=getchar();
while(p>'9'||p<'0')
{
if(p=='-')
f=-1;
p=getchar();
}
while(p>='0'&&p<='9')
{
r=r*10+p-48;
p=getchar();
}
return r*f;
}
int main()
{
T=read();
while(T--)
{
memset(v,0,sizeof(v));
ans=0;
n=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
if(!v[a[i]])
{
ans++;
for(int j=1;j<=a[n];j++)
{
if(v[j]&&j+a[i]<=a[n])
v[j+a[i]]=1;
if(j%a[i]==0)
v[j]=1;
}
}
printf("%d\n",ans);
}
return 0;
}
/*
2
4
3 19 10 6
5
11 29 13 19 17
*/
题解&反思
没做过原题*3
考场上想出正解但是算错复杂度,就写了55暴力挂一个假的二分+贪心,民间数据得分在90~100之间正式成绩说不定就卡成55了……
正解也容易,就是二分+贪心,贪心是设f[u]为u最长能向上贡献的链,treedp的过程中,每次凑出一个长度>=mid的链就tot++,最后看是否tot>=m
具体的判断是先把一个点u长度>=mid的f[son]直接加tot,剩下f[son]的排个序,然后先用双指针看最多能两两拼多少条长度>=mid的链,如果全都用上就f[u]=0,否则二分f[u]的最大值,判断就是删掉一个然后看是否还能拼出那么多条>=mid的链
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500005;
int n,m,h[N],cnt,f[N],tot,a[N],top;
struct qwe
{
int ne,to,va;
}e[N<<1];
int read()
{
int r=0,f=1;
char p=getchar();
while(p>'9'||p<'0')
{
if(p=='-')
f=-1;
p=getchar();
}
while(p>='0'&&p<='9')
{
r=r*10+p-48;
p=getchar();
}
return r*f;
}
void add(int u,int v,int w)
{
cnt++;
e[cnt].ne=h[u];
e[cnt].to=v;
e[cnt].va=w;
h[u]=cnt;
}
int wk(int de,int w)
{
int l=1,r=top,co=0;
while(l<r)
{
if(r==de)
r--;
while(l<r&&a[l]+a[r]<w)
l++;
if(l==de)
l++;
if(l<r)
co++,l++,r--;
}
return co;
}
void dfs(int u,int fa,int w)
{
for(int i=h[u];i;i=e[i].ne)
if(e[i].to!=fa)
dfs(e[i].to,u,w);
top=0;
for(int i=h[u];i;i=e[i].ne)
if(e[i].to!=fa)
f[e[i].to]+e[i].va>=w?tot++:a[++top]=f[e[i].to]+e[i].va;
sort(a+1,a+1+top);
int con=wk(top+1,w);
tot+=con;
if(2*con==top)
f[u]=0;
else
{
int l=1,r=top;
while(l<=r)
{
int mid=(l+r)>>1;
if(wk(mid,w)==con)
l=mid+1,f[u]=a[mid];
else
r=mid-1;
}
}
}
bool ok(int w)
{
tot=0;
dfs(1,0,w);
return tot>=m;
}
int main()
{
n=read(),m=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read(),z=read();
add(x,y,z),add(y,x,z);
}
int l=0,r=5e8,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(ok(mid))
l=mid+1,ans=mid;
else
r=mid-1;
}
printf("%d\n",ans);
return 0;
}
总结
还没出分……总之应该除了t3虚以外发挥还可以orz