2019.8.22 TEST

Posted ljb666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019.8.22 TEST相关的知识,希望对你有一定的参考价值。

回学校后的第一次测试,考的非常差,三道题都还是比较有一些思维难度的。算法都远远在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的主要内容,如果未能解决你的问题,请参考以下文章

testtest

显示员工的销售额

请教shell脚本处理带空格的文件名

python argparse中的多个参数

call和apply的使用

textarea 删除白线?