码迷,mamicode.com
首页 > 其他好文 > 详细

2019.8.22 TEST

时间:2019-08-22 22:19:39      阅读:107      评论:0      收藏:0      [点我收藏+]

标签:方案   判断   hash   推荐   event   problem   枚举   pair   hide   

回学校后的第一次测试,考的非常差,三道题都还是比较有一些思维难度的。算法都远远在noip的范围内。

T1 count

技术图片

这道题最开始一看似乎是找规律的题,首先都至少有两种选法。这些被切掉的块都必须是n的因数,手推了几个例子似乎找到了一些规律,可实际上确是wa完了。

其实就是对子树的统计,如果我们要选一个点,必须要把这个点的子树全部选完,用反证法可以证明的,如果没有把子树选完,剩下的部分构成的块一定不符合要求。子树的割法是唯一的。

dfs就可以了,另外还要预处理出n的因数,2-sqrt(n)。然后dfs的时候判断是否有满足条件的子树,子树大小必须是n的约数,如果不够就将子树和向上累加,找到了满足条件的子树就把那一整块的size赋为0。最后看根节点的size是否为0,一旦为0的话,方案就是成立的。

代码如下:

技术图片
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
struct node{
    int nxt,to;
}edge[maxn*2];
int head[maxn],cnt;
inline int R() {
    int a=0;char c=getchar();
    while(c>9||c<0)c=getchar();
    while(c>=0&&c<=9)a=a*10+c-0,c=getchar();
    return a;
}
void add(int x,int y){
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;
    head[x]=cnt;
}
int fa[maxn],size[maxn];
int n,x,y,tot,ans,ljb;
int di[maxn];
bool flag;
void dfs1(int x,int f){
    fa[x]=f;
    for(register int i=head[x];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==fa[x]) continue;
        dfs1(v,x);
    }
}
void dfs2(int now,int num){
    size[now]=1;
    for(register int i=head[now];i;i=edge[i].nxt){
        int v=edge[i].to;
        if(v==fa[now]) continue;
        dfs2(v,num);
        size[now]+=size[v];
        if(size[now]>num) return;
    }
    if(size[now]==num) size[now]=0;//如果有一个num的子树 直接赋为0
}
vector<int> sb;//用vector储存要好一点。 
void divide(int x){
    for(register int i=2;i<=sqrt(x);i++){
        if(x%i==0){
            if(i*i==x) sb.push_back(i);
            else sb.push_back(i),sb.push_back(x/i);
        }
    } 
}
int main(){
    n=R();
    for(register int i=1;i<n;i++){
        x=R();y=R();
        add(x,y);add(y,x);
    }
    dfs1(1,0);
    divide(n);
    for(register int i=0;i<sb.size();i++){
        dfs2(1,sb[i]);
        if(!size[1]) ans++;//1为根,最后所有子树必须被选完 
    }
    printf("%d\n",ans+2);
    return 0;
} 
View Code

T2 dinner

技术图片

技术图片

最开始看这道题的时候以为是dp,不过发现状态很难转移,数据范围也不允许。

又觉得可以二分答案,将所有人划分为m段,以为是像简单的数列分段那样,和的最大值最小,但发现样例都过不了。

下来评讲的时候听到一堆二分套二分,倍增的神仙解法。

其实这道题就可以看作把一个圆分成m段,使每段的和的最大值最小。

关于圆的套路便是破环成链,将原来的数组复制一份即可。

而我们这样的话可以从任意一个人为起点开始,所以二分答案check的时候还要枚举以每个人为起点的情况。

check的时候有一个小剪枝,tot统计从1开始的数的和,超过了二分的mid就直接break,表示这是真的非常神奇。不然直接100->60。

虽然复杂度达到了n^2logn。不过比什么二分套二分之类的好理解多了。在学校的机子上也可以A。

技术图片
//一句话题意,就是一个圆划分成m段每段和最大值最小
//以下是最好理解的一种做法 
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
int n,m;
int t[maxn];
int sum[maxn];
int maxx;
int l,r;
int ans;
bool check(int x){
    int tot=0;
    for(int i=1;i<=n;i++){
        tot+=t[i];
        if(tot>x) break;//当前的和已经超出了答案,相当于一个小小的剪枝,不加这两句就是60。 
        int tmp=0,cnt=1;
        for(int j=i;j<i+n;j++){//枚举区间还可以从哪个点开始 
            tmp+=t[j];
            if(tmp>x){
                tmp=t[j];
                cnt++;
            }
        } 
        if(cnt<=m) return true;
    } 
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&t[i]);//破环为链 
        maxx=max(maxx,t[i]);
        t[i+n]=t[i];
        r+=t[i];
    }
    l=maxx;//二分边界,至少都是数列中的最大值 
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            r=mid-1;    
        }
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}
View Code

T3 chess

技术图片

技术图片

技术图片

洛谷原题:白银莲花池

一开始是真的脑残,以为直接就是两遍dfs的事情,但这样时间复杂度极高,但发现两个样例都是秒过,还是可以得部分分。结果交上去只有10分。而洛谷数据水还可以骗51分。

sb代码:

技术图片
#include<bits/stdc++.h>
using namespace std;
const int N=550;
int n,m;
int ditu[N][N];
int stx,edx,sty,edy;
long long minn;
bool used[N][N];
int dx[]={0,1,1,-1,-1,2,2,-2,-2};
int dy[]={0,2,-2,2,-2,1,-1,1,-1};
bool flag;
long long tot;
void dfs1(int x,int y,long long bs){
    if(x==edx&&y==edy){
        flag=true;
        if(bs<minn){
            minn=bs;
            return;
        }
    }
    if(bs>minn) return;
    if(ditu[x][y]==2) return;
    for(int i=1;i<=8;i++){
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!used[xx][yy]){
            used[xx][yy]=true;
            if(ditu[xx][yy]==1||ditu[xx][yy]==4) dfs1(xx,yy,bs);
            if(ditu[xx][yy]==0) dfs1(xx,yy,bs+1);
            used[xx][yy]=false;
        }
    }
}
void dfs2(int x,int y,long long bs){
    if(x==edx&&y==edy&&bs==minn){
        tot++;
        return;
    }
    if(bs>minn) return;
    if(ditu[x][y]==2) return;
    for(int i=1;i<=8;i++){
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx>=1&&xx<=n&&y>=1&&y<=m&&!used[xx][yy]){
            used[xx][yy]=true;
            if(ditu[xx][yy]==1||ditu[xx][yy]==4) dfs2(xx,yy,bs);
            if(ditu[xx][yy]==0) dfs2(xx,yy,bs+1);
            used[xx][yy]=false;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    minn=0x7fffffff;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&ditu[i][j]);
            if(ditu[i][j]==3) stx=i,sty=j;
            if(ditu[i][j]==4) edx=i,edy=j;
        }
    }
    dfs1(stx,sty,0);
    dfs2(stx,sty,0);
    if(!flag){
        printf("-1\n");
        return 0;
    } 
    printf("%lld\n",minn);
    printf("%lld\n",tot);
    return 0;
} 
View Code

最后讲评的时候发现可以转化为最短路的模型,然后方案数就是最短路计数。对于所有的0号节点(包括对方的帅及初始起点)建单向边,因为你只能跳过去,不能再回来。建边的问题就一眼看出来了,将所有的空格点都连上一条边权为1的边,遇上友军就不连,相当于是连一条边权为0的边,那些敌军也不要管。最后直接跑dijkstra+最短路计数即可。

关于最短路计数,就是dp,此处不再展开,推荐两道题:

P1608 路径统计

P1144 最短路计数

代码如下:

技术图片
//这种图论建模的思想一定要学会。 
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
const int inf=0x7f7f7f;
const int N=550;
struct node{
    int nxt,to,val;
}edge[maxn*3];
int head[maxn],cnt;
int n,m;
long long dp[maxn];
void add(int x,int y,int v){
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;
    edge[cnt].val=v;
    head[x]=cnt;
}
bool vis[maxn];
long long dis[maxn];
int hash[N][N];
int ditu[N][N];
int dx[]={0,1,1,-1,-1,2,2,-2,-2};
int dy[]={0,2,-2,2,-2,1,-1,1,-1}; 
int stx,sty,edx,edy;
int st,ed;
int used[N][N];
priority_queue< pair<long long ,int> >q;
void dfs(int u,int x,int y){
    used[x][y]=true;
    for(int i=1;i<=8;i++){
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!used[xx][yy]){
            if(ditu[xx][yy]==1) dfs(u,xx,yy);//继续搜 
            else{
                used[xx][yy]=true;
                add(u,hash[xx][yy],1);//所有空格连边 
            }
        }
    }
}
void dijkstra(int x){
    for(int i=1;i<=n*m;i++) dis[i]=inf;
    memset(vis,false,sizeof(vis));
    q.push(make_pair(0,x));
    dis[x]=0;
    dp[x]=1;
    //vis[x]=true;//dijkstra这里不用写!! 
    while(q.size()){
        int u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val){
                dp[v]=dp[u];
                dis[v]=dis[u]+edge[i].val;
                q.push(make_pair(-dis[v],v));
            }
            else if(dis[v]==dis[u]+edge[i].val){
                dp[v]+=dp[u];
            }
        }
    }
}
int main(){
    freopen("chess.in","r",stdin);
    freopen("chess.out","w",stdout); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            hash[i][j]=(i-1)*m+j;//每个点坐标hash成序号 
        } 
    } 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&ditu[i][j]);
            if(ditu[i][j]==3) stx=i,sty=j,st=hash[i][j];
            if(ditu[i][j]==4) edx=i,edy=j,ed=hash[i][j]; 
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(ditu[i][j]==0||ditu[i][j]==3){//空格以及起点与所有点连边,连有向边 
                memset(used,0,sizeof(used));
                dfs(hash[i][j],i,j);//处理每一个点的连边 
            }
        }
    }
    dijkstra(st);
    if(dis[ed]<inf){
        printf("%lld\n",dis[ed]-1);//走到敌军阵营不消耗步数 
        printf("%lld\n",dp[ed]);
    }
    else printf("-1\n");
    return 0;
} 
View Code

 

2019.8.22 TEST

标签:方案   判断   hash   推荐   event   problem   枚举   pair   hide   

原文地址:https://www.cnblogs.com/LJB666/p/11396864.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!