1. 程式人生 > 實用技巧 >8月20日考試題解(思維題+貪心+動態規劃)

8月20日考試題解(思維題+貪心+動態規劃)

T2打暴力都能拿80分,可怕。

T1

題目大意:給定一個實數序列$A$。設$S=\sum_{i=1}^n A_i$。你可以做下列操作$n$次:

選擇兩個未被選過的下標$i,j$,將$A_i$變為不超過$A_i$的最大整數,將$A_j$變為不小於$A_j$的最小整數。要求操作完成後新的序列中元素之和與原來的差值的絕對值儘可能小。現給定一段序列和多個詢問區間,要求對詢問區間求解。

顯然整數對答案是沒有影響的。設$cnt$表示區間內的小數個數,$sum$表示區間原來的和,$top$表示區間內所有數都上取整後的和,$n$表示區間最多的操作次數。

顯然$top$不是最優的答案,肯定有一些數要選擇下取整。對應的操作就是$top$減$1$。但有一些限制條件,要進行分類討論:

1.$top$不管怎麼減。其值都是大於原來的值的。我們直接減就好。

2.注意到如果有最優答案,其值和原來序列和的差值不超過$0.5$。因為如果大於$0.5$我們總有辦法將差值調整到小於$0.5$。所以我們在$cnt$,$n$,$floor(top-sum+0.5)$間取$\min$。如果取到最優答案了就先用整數頂上去,整數不夠了再用小數,以保證答案的最優性。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=500005;
int n,q,l,r;
double sum[maxn],t[maxn],x,c[maxn];
int main() { //freopen("data.in","r",stdin); //freopen("ans.out","w",stdout); scanf("%d%d",&n,&q); for (int i=1;i<=n;i++) { scanf("%lf",&x); sum[i]=sum[i-1]+x; t[i]=t[i-1]+ceil(x); c[i]=c[i-1]+(ceil(x)!=floor(x)); } while(q--) { scanf(
"%d%d",&l,&r); double n=(double)(r-l+1)/(double)2; double cnt=c[r]-c[l-1],zs=(double)(r-l+1)-cnt; double top=t[r]-t[l-1],ss=sum[r]-sum[l-1]; if (top-min(cnt,n)>ss) top-=min(cnt,n); else { double v=min(min(cnt,n),floor(top-ss+0.5)),t=n-v; top-=v; if (zs<t) top-=t-zs; } printf("%.3lf\n",abs(top-ss)); } return 0; }

T2

題目大意:給定一棵含有$n$個結點的樹,每條邊長度為1。有$m$個形如$A,B,D$的限制條件,意為找到距離$A$和$B$的總長度不超過$D$的點$x$。要求找到滿足這$m$個條件的任意一點,如果沒有輸出$NO$。

一道神奇的貪心題,但並不會證明其正確性。直接說做法:

根據題意,我們將限制條件轉化為$dis(A,x)+dis(B,x)\leq D$

設$dist(x,A,B)$表示$x$到鏈$AB$的距離,則有:

$dist(x,A,B)*2+dis(A,B)\leq D$

接下來我們推式子:

$dist(x,A,B)*2\leq D-dis(A,B)$

$dist(x,A,B)\leq \frac{D-dis(A,B)}{2}$

注意到樹上路徑長度可以用點的深度表示,我們有:

$dep[x]\geq dep[LCA(A,B)]-\frac{D-dis(A,B)}{2}$

易知$dis(A,B)=dep[A]+dep[B]-2*dep[LCA(A,B)]$,轉化為:

$dep[x]\geq \frac{dep[A]+dep[B]-D}{2}$

設不等式右邊的值為$w[i]$,顯然我們要找到這個最大的$w[i]$,且可以找出對應的$A,B$。然後我們遍歷一遍樹,找到滿足這個條件的且深度最小的點,看這個點能不能滿足其他的限制條件。如果不能則無解。反則此點為答案。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int dep[maxn][5],n,m;
int s[maxn],t[maxn],d[maxn];
int head[maxn],cnt,mx,pos;
struct node
{
    int next,to;
}edge[maxn*2];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void add(int from,int to)
{
    edge[++cnt].next=head[from];
    edge[cnt].to=to;
    head[from]=cnt;
}
inline void dfs(int now,int fa,int opt)
{
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (to==fa) continue;
        dep[to][opt]=dep[now][opt]+1;
        dfs(to,now,opt);
    }
}
signed main()
{
    n=read();m=read();
    for (int i=1;i<n;i++)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    dfs(1,0,0);
    for (int i=1;i<=m;i++)
    {
        s[i]=read(),t[i]=read(),d[i]=read();
        mx=max(mx,max(0,dep[s[i]][0]+dep[t[i]][0]-d[i]));
    }
    for (int i=1;i<=m;i++)
        if (max(0,dep[s[i]][0]+dep[t[i]][0]-d[i])==mx)
        {
            dfs(s[i],0,1),dfs(t[i],0,2),mx=d[i];break;
        }
    for (int i=1;i<=n;i++)
        if (dep[i][1]+dep[i][2]<=mx&&(!pos||dep[i][0]<dep[pos][0])) pos=i;
    dfs(pos,0,3);
    for (int i=1;i<=m;i++)
        if (dep[s[i]][3]+dep[t[i]][3]>d[i])
        {
            printf("NO");
            return 0;
        }
    printf("%d",pos);
    return 0;
}

T3

題目大意:一個數可以寫成多個$2$的次方之和。現給定$T$個數,求解數的拆分方案。例如,$4$有$4$種方案:

$4=2^0+2^0+2^0+2^0$

$4=2^1+2^0+2^0$

$4=2^1+2^1$

$4=2^2$

只會遞推的分,$n\leq 10^7$。打表可以發現:

if (i%2) ans[i]=ans[i-1]+ans[i/2+1];
else ans[i]=ans[i-1]+ans[i/2];

正解動態規劃。咕咕咕,沒看懂QAQ。如果明白了會補上。