1. 程式人生 > 實用技巧 >10月8日考試 題解 (貪心+模擬+樹鏈剖分+搜尋)

10月8日考試 題解 (貪心+模擬+樹鏈剖分+搜尋)

T1 小Z搭積木

題目大意:小Z有$n$塊積木。每個積木上面最多搭$a_i$塊積木,積木可以擺很多列。問最少的列數。$n\leq 5000$

先把$a$排序,然後從上往下搭積木,看哪個積木沒被用且$a$儘可能小。時間複雜度$O(n^2)$。

程式碼:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n,a[5005],v[5005],ans,m;
int main(){
    cin>>n;
    for (int i=1;i<=n;i++) scanf("
%d",&a[i]); sort(a+1,a+n+1); for (int i=1;i<=n;i++) if(!v[i]){ ans++; m=1; for (int j=i+1;j<=n;j++) if (!v[j]&&a[j]>=m){ v[j]=1; m++; } } cout<<ans; return 0; }

T2 動態仙人掌

題目大意:數軸上有$n$個位置在$p_i$,高度為$h_i$的障礙。人可以向與正方向夾角45度的方向起跳,沿著與負方向夾角45度降落。一個障礙被越過當人在此位置時的高度大於等於障礙的高度。人可以在地面任意時刻起跳,在空中任意時刻降落,但是不能在空中起跳。問越過所有障礙最少跳多高。$n\leq 3 \times 10^5$

一開始想假了,距離正解只差一步之遙……

顯然對於每個障礙都有一個最晚的起跳點和最早的降落點。我們稱其為一段區間。當兩個區間有交集時,這段是不能降落的(因為再跳起達不到後一個障礙的高度)。所以我們不妨考慮區間合併,以$l$為關鍵字排序。噹噹前區間的$l$嚴格小於前面區間的$r$時合併區間,否則更新答案。

時間複雜度$O(n\log n)$。

程式碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=300005;
int n;
struct
node { int l,r; }a[N]; 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; } bool cmp(node x,node y) { return x.l<y.l; } int main() { n=read(); for (int i=1;i<=n;i++) { int p=read(),h=read(); a[i].l=p-h,a[i].r=p+h; } sort(a+1,a+n+1,cmp); if (a[1].l<0){ cout<<-1; return 0; } int nxt=a[1].r,pre=a[1].l; double ans=0; for (int i=2;i<=n;i++) { if (a[i].l<nxt){ nxt=max(nxt,a[i].r); }else{ ans=max(ans,(nxt-pre)*1.0/2); pre=a[i].l,nxt=a[i].r; } } ans=max(ans,(nxt-pre)*1.0/2); printf("%.1lf",ans); return 0; }

T3 相交

題目大意:有一個含有$n$個結點的樹,$q$次詢問。每次詢問形如$(a,b,c,d)$,表示給$(a,b)$路徑所有點打上標記,詢問$(c,d)$路徑上有沒有被標記的點。詢問完後$(a,b)$路徑上的標記會消失。$n,q\leq 10^5$

傻逼題。直接上樹剖就行了。

程式碼:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=100005;
int size[N],son[N],dep[N],fa[N],n,q;
int dfn[N],top[N],tot;
int head[N],cnt;
int sum[N*4],lazy[N*4];
struct node
{
    int next,to;
}edge[N*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]=(node){head[from],to};
    head[from]=cnt;
}
inline void dfs_son(int now,int f)
{
    size[now]=1;
    dep[now]=dep[f]+1;fa[now]=f;
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if (to==f) continue;
        dfs_son(to,now);
        size[now]+=size[to];
        if (size[to]>size[son[now]]) son[now]=to;
    }
}
inline void dfs_chain(int now,int topf)
{
    dfn[now]=++tot;
    top[now]=topf;
    if (!son[now]) return;
    dfs_chain(son[now],topf);
    for (int i=head[now];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(dfn[to]) continue;
        dfs_chain(to,to);
    }
}
inline void pushdown(int index,int l,int r)
{
    int mid=(l+r)>>1,k=lazy[index];
    lazy[index]=0;
    sum[index*2]+=(mid-l+1)*k;
    sum[index*2+1]+=(r-mid)*k;
    lazy[index*2]+=k;lazy[index*2+1]+=k;
}
inline void update(int index,int l,int r,int ql,int qr,int k)
{
    if (ql<=l&&r<=qr)
    {
        sum[index]+=(r-l+1)*k;
        lazy[index]+=k;
        return;
    }
    pushdown(index,l,r);
    int mid=(l+r)>>1;
    if(ql<=mid) update(index*2,l,mid,ql,qr,k);
    if(qr>mid) update(index*2+1,mid+1,r,ql,qr,k);
    sum[index]=sum[index*2]+sum[index*2+1];
}
inline int query(int index,int l,int r,int ql,int qr)
{
    if (ql<=l&&r<=qr) return sum[index];
    pushdown(index,l,r);
    int mid=(l+r)>>1,res=0;
    if (ql<=mid) res+=query(index*2,l,mid,ql,qr);
    if (qr>mid) res+=query(index*2+1,mid+1,r,ql,qr);
    return res;
}
inline void updrange(int x,int y,int k)
{
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    update(1,1,n,dfn[x],dfn[y],k);
}
inline int qrange(int x,int y)
{
    int res=0;
    while(top[x]!=top[y])
    {
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        res=(res+query(1,1,n,dfn[top[x]],dfn[x]));
        x=fa[top[x]];
    }
    if (dep[x]>dep[y]) swap(x,y);
    res=(res+query(1,1,n,dfn[x],dfn[y]));
    return res;
}
int main()
{
    n=read();
    for (int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    dfs_son(1,0);
    dfs_chain(1,1);
    q=read();
    while(q--)
    {
        int a=read(),b=read(),c=read(),d=read();
        updrange(a,b,1);
        int tmp=qrange(c,d);
        if (tmp>0) printf("YES\n");
        else printf("NO\n");
        updrange(a,b,-1);
    }
    return 0;
}

T4 聰明格

題目大意:給定一個$n \times n$的棋盤,現要求往裡面填$1-n$的數,使得每一行每一列沒有重複的數字。同時給定一個$n \times n$的圖,相同的數字構成一個聯通塊,表示棋盤內對應連通塊內數字的積要等於圖中的數。問有幾種方案,並輸出字典序最小的方案數。

爆搜+剪枝最多70pts.正解的搜尋策略很妙。

考慮到對於連通塊的限制比對於行和列的限制要嚴格的多,所以我們不妨考慮這樣一種策略:先把連通塊填滿,然後判斷是否合法。接下來是剪枝:我們將連通塊按照大小排序,如果大小相同按照因數個數排序(這樣使選擇的餘地儘可能小),然後將每個連通塊填滿即可。

時間複雜度$O(metaphysics)$。

程式碼:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=11,M=505;
const int dx[]={0,1,-1,0,0};
const int dy[]={0,0,0,1,-1};
struct Node{
    int c[N][N];
}ans[M];
struct node{
    int x,y;
};
inline void debug(){
    puts("fuck!");
}
bool operator < (const Node x,const Node y)
{
    for (int i=1;i<N;i++)
        for (int j=1;j<N;j++)
            if (x.c[i][j]!=y.c[i][j])
                return x.c[i][j]<y.c[i][j];
    return 0;
}
vector<node> v[M];
vector<int> p[M];
int a[N][N],l[N][N],h[N][N],vis[N][N],c[N][N],n,cnt,tot;
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 int factor(int x)
{
    int t=0;
    for (int i=1;i*i<=x;i++)
        t+=(int)(x%i==0);
    return t;
}
bool cmp(vector<node> x,vector<node> y)
{
    if (x.size()==y.size()) 
        return factor(x[0].x)<factor(y[0].x);
    return x.size()<y.size();
}
inline void split(int t,int x)
{
    for (int i=1;i<=n;i++)
        if (x%i==0) p[t].push_back(i);
}
inline bool judge(int x,int y)
{
    if (x<1||x>n||y<1||y>n||vis[x][y]) return 0;
    return 1;
}
inline void dfs(int x,int y)
{
    vis[x][y]=1;
    v[cnt].push_back((node){x,y});
    for (int i=1;i<=4;i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        if (judge(xx,yy)&&a[xx][yy]==a[x][y])
            dfs(xx,yy);
    }
}
void work(int dep);
inline void fill(int t,int dep,int s,int tot)
{
    if (dep>tot) work(t+1);
    else
    {
        int x=v[t][dep].x,y=v[t][dep].y;
        for (int i=0;i<p[t].size();i++)
        {
            int u=p[t][i];
            if ((s==u||dep<tot)&&s%u==0&&!h[x][u]&&!l[y][u])
            {
                h[x][u]=l[y][u]=1;
                c[x][y]=u;
                fill(t,dep+1,s/u,tot);
                c[x][y]=0;
                h[x][u]=l[y][u]=0;
            }
        }
    }
}
inline void work(int dep)
{
    if (dep>cnt){
        tot++;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                ans[tot].c[i][j]=c[i][j];
    }
    else{
        int num=v[dep][0].x,tot=v[dep].size()-1;
        fill(dep,1,num,tot);
    }
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            a[i][j]=read();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            if (vis[i][j]) continue;
            cnt++;v[cnt].push_back((node){a[i][j],0});
            dfs(i,j);
        }
    sort(v+1,v+cnt+1,cmp);
    for (int i=1;i<=cnt;i++)
        split(i,v[i][0].x);
    work(1);
    printf("%d\n",tot);
    Node t=ans[1];
    for (int i=2;i<=tot;i++)
        t=min(t,ans[i]);
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
            printf("%d ",t.c[i][j]);
        printf("\n");
    }
    return 0;
}