swust 2014届选拔赛 题解

   题目传送门:http://www.oj.swust.edu.cn/contest/show/1160

A: 快乐的Fm

这是一道dp。我们假设这n个人按照水平排成一排,   可以很容易想到用dp[i][j]表示: 排到第第i个位置的人面向左边的是第j个部位的总方案数。可以有转移

  dp[i][j]+=dp[i-1][k] ( 只要k和j 不发生冲突就可以转移 )。总复杂度(O(16n))

AC 代码。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 100010;
const int mod = 522462628;
int dir[mmax];
int dp[mmax][5];
int main()
{
    int n;
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&dir[i]);
        memset(dp,0,sizeof dp);
        dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1;
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=4;j++)
            {
                for(int k=1;k<=4;k++)
                {
                    if(k==2 && abs(j-dir[i-1])==2 )
                        continue;
                    if(j==4 && k==dir[i])
                        continue;
                    dp[i][j]+=dp[i-1][k];
                    dp[i][j]%=mod;
                }
            }
        }
        int ans=0;
        for(int i=1;i<=4;i++)
        {
            ans+=dp[n][i];
            ans%=mod;
        }
        cout<<ans<<endl;
    }
    return 0;
}

B: 奔跑的Fm

按照题意实际上只要是的所有的点都能联通就可以了。因为走过的路程不需要在走了,所以实际上就是求一个最小生成树。只是这里的边的花费是2维的,那么我们可以自定义边权按照 len最小,其次按照cost最小排序。由于点只有1000个  ,所以用prim和kruskal算法都能通过。复杂度分别为O(n^2)和O(mlgm);

kruskal代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 1010;
struct edge
{
    int st,en;
    int len,cost;
    bool operator < (const edge &a) const
    {
        if(len==a.len)
            return cost<a.cost;
        return len<a.len;
    }
}E[10010];
map<string,int>q;
bool ok(int x)
{
    while(x)
    {
        if(x%10 == 9)
            return 0;
        x/=10;
    }
    return 1;
}
int fa[mmax];
int find(int x)
{
    if(x==fa[x])
        return x;
    return fa[x]=find(fa[x]);
}
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int n,m,len,cost,cnt,num;
    string u,v;
    while(cin>>n>>m)
    {
        q.clear();
        cnt=num=0;
        for(int i=0;i<m;i++)
        {
            cin>>u>>v>>len>>cost;
            if(!q.count(u))
                q[u]=++cnt;
            if(!q.count(v))
                q[v]=++cnt;
            if(ok(len) && ok(cost))
            {
                E[num].st=q[u];
                E[num].en=q[v];
                E[num].len=len;
                E[num].cost=cost;
                num++;
            }
        }
        for(int i=1;i<=n;i++)
            fa[i]=i;
        sort(E,E+num);
        len=0,cost=0;
        for(int i=0;i<num;i++)
        {
            int u=find(E[i].st);
            int v=find(E[i].en);
            if(u!=v)
            {
                fa[u]=v;
                len+=E[i].len;
                cost+=E[i].cost;
            }
        }
        cnt = 0;
        for(int i=1;i<=n;i++)
        {
            if(find(i)==i)
                cnt++;
        }
        if(cnt>=2)
            puts("Don't touch me!");
        else
            cout<<"totlen: "<<len<<" mincost: "<<cost<<endl;
    }
    return 0;
}

C: 幸运的Fm


关于区间查询有的计数问题,只有查询没有更新的这类问题我也讲过了,首先我们预处理出每个位置的f(a[i])的值,然后,然后用sum[i]记录到第i个位置的前缀和,那么要查询区间[l,r]上的答案是, ans=sum[r]-sum[l-1] (根本用不到线段树,线段树是用来处理有更新的)。现在的关键问题在于怎么快速求出每个a[i]的f值。有一种的n√a(max)的做法,就是对于每个a[i]暴力质因子分解。    还有一种很高效的方法。  如果你发现 f[x]=f[x/d]+1(d是x的一个质因子),那么如果对于每一个数我们找出了他的一个质因子,我们就可 O(n)递推出所有的1-1000000的f值。  对于求出每个数的一个质因子,我们可以按照素数筛选那样,只是标记哪里多一个记录质因子。  复杂是nlga(max)。  这题本来一开始数据是卡了√n的做法的。后来在我的强烈要求下时间限制标称2s。放过了这种做法。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#include<queue>
#define debug
using namespace std;
const int mmax = 1000010;
int num[mmax],c[mmax];
int sum[50010];
bool isprime[mmax];
void pre()//nlgn的预处理方法
{
    memset(isprime,1,sizeof isprime);
    num[1]=0;
    isprime[1]=0;
    for(int i=2;i<mmax;i++)
    {
        if(isprime[i])
        {
            for(int j=2*i;j<mmax;j+=i)
            {
                isprime[j]=0;
                c[j]=i;
            }
        }
    }
    for(int i=2;i<mmax;i++)
    {
        if(isprime[i])
            num[i]=1;
        else
            num[i]=num[i/c[i]]+1;
    }
}
int ff(int x)//暴力质因子分解
{
    int cnt;
    for(int i=2;i*i<=x;i++)
    {
        while(x%i==0)
        {
            cnt++;
            x/=i;
        }
    }
    if(x>1)
        cnt++;
    return cnt;
}
int main()
{
    int n,q;
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    pre();
    while(cin>>n>>q&& n+q)
    {
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            sum[i]=sum[i-1]+num[x];//   +ff(x) 为暴力
        }
        while(q--)
        {
            int l,r;
            scanf("%d %d",&l,&r);
            printf("%d\n",sum[r]-sum[l-1]);
        }
    }
    return 0;
}

D: 苦恼的Fm

这题最后实际上就是2个人比赛了。对于n-1个人  只需要最大的m张牌就够了,其他的牌都没有用的。  有2种做法,第一种是贪心。对于fm出的最大的牌,对方如果能赢,就用最大的牌去赢了好了,就是对于fm的牌,我么重大到小如果能赢就赢,这么保证最优。   另外一种做法。对于fm的每一张牌i,对于对面的牌j如果能赢i(j>i)就建立边i->j。那么你会发现最后是一个2分图,如果要fm赢得最少,就要选出尽量多的边,没选一条边代表Fm输了,由于每张牌只能选一次,那么实际上就是在2分图上面找到最大匹配。当然贪心的做法复杂度最好 O(nlgn)
贪心做法:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define debug
bool flag[10005];
int s[10005];
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        memset(flag,false,sizeof(flag));
        for(int i=0;i<m;i++)
        {
            scanf("%d",&s[i]);
            flag[s[i]]=true;
        }
        sort(s,s+m);
        int ans=0;
        for(int i=0;i<m;i++)
        {
            bool k=false;
            for(int j=s[i]+1;j<=n*m;j++)
            {
                if(!flag[j])
                {
                    k=true;
                    flag[j]=true;
                    break;
                }
            }
            if(!k)
                ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

匹配做法:
/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 110;
bool G[mmax][mmax];
int card1[mmax];
int card2[mmax];
map<int,int>q;
int link[mmax];
bool vis[mmax];
bool match(int x,int n)
{
    for(int i=0;i<n;i++)
    {
        if( G[x][i]  && !vis[i])
        {
            vis[i]=1;
            if(link[i]==-1 || match(link[i],n))
            {
                link[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
int hungury(int n)
{
    int cnt=0;
    memset(link,-1,sizeof link);
    for(int i=0;i<n;i++)
    {
        memset(vis,0,sizeof vis);
        if(match(i,n))
            cnt++;
    }
    return cnt;
}
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int n,m;
    while(cin>>n>>m)
    {
        memset(G,0,sizeof G);
        q.clear();
        for(int i=0;i<m;i++)
        {
            scanf("%d",&card1[i]);
            q[card1[i]]=1;
        }
        int cnt=0;
        for(int i=n*m;i>=1;i--)
        {
            if(!q.count(i) && cnt<m)
                card2[cnt++]=i;
        }
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(card1[i]<card2[j])
                    G[i][j]=1;
            }
        }
        printf("%d\n",m-hungury(m));
         
    }  
    return 0;
}

E: Fm的旅行

最后题目就是求生成树的个数,由于边权都是1,就不存在最小生成树了(生成树都是最小了)。 我们考虑每一个边上的正m边形。 如果是生成树,那么不能形成环,首先假设每个m边形都是自联通(不通过其他的m边形),那么显然会形成一个环,每个m边形都可以通过其他m边形联通,又自联通,所以就形成环。那么我们需要有一个m变形不自联通,这样就不会形成 环,而且保证所有点都联通(自己YY一下)。  这个m边形有n个, 选出一个不自连通, 然后其他的都要自联通,每个m自联通只需要在删除一条边,所以有m种方法, 而对于不自联通的一个m变形,首先n变形上的边是不能选的,还要删除一条边,那么就有m-1种方法。 总的方案数 为  ans = n*(m-1)*m^(n-1)。
VIEW CODE
/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#include <map>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 110;
const int mod = 10007;
int Pow_mod(int x,int y)
{
    int res=1;
    for(;y;y/=2)
    {
        if(y&1)
            res=res*x%mod;
        x=x*x%mod;
    }
    return res;
}
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int n,m;
    while(cin>>n>>m)
    {
        int ans=Pow_mod(m,n-1);
        ans*=n;
        ans%=mod;
        ans*=(m-1);
        ans%=mod;
        cout<<ans<<endl;
    }
    return 0;
}

F: 只要出题人开心就好

纯粹的数学变换。


方法一


把(Xi+Yj)*(i+j) 拆开  有  Xi*(i+j) +Yj*(i+j)
分别考虑  Xi,Yj被乘了多少。 对于一个Xi  会枚举 j =1 -> m 那么 
就有 Xi*(i+1+i+2+i+3+i+4...+i+m  )  化简有 Xi*(m*i  +   (1+m)*m/2  )
同理 Yj*(j+1+j+2+j+3+...+j+n)  化简有Yj(n*j+(1+n)*n/2)
那么 最后 有
LL ans=0;
for(int i=1;i<=n;i++)
ans+=Xi*(m*i+(1+m)*m/2);
for(int i=1;i<=m;i++)
ans+=Yi*(n*i+(1+n)*n/2);
注意会超出int 范围
本题很好想,考验代码能力,当然想清楚了就写得很快。


方法二 
由于 1<=Xi,Yj<=1000
而 N 是 10^5,必然Xi有很多重复,很多都是重复计算
用一个数组c1[i] ,表示 Xi中每个数i出现的次数
用一个数组c2[i] ,表示 Yi中每个数i出现的次数
用一个数组d1[i] ,表示 Xi中每个数i乘的系数
用一个数组d2[i] ,表示 Yi中每个数i乘的系数
那么有
for(int x=1;x<=1000;x++)
for(int y=1;y<=1000;y++)
{
if(c1[x]>0 &&c2[y]>0)
ans+=(x+y)*(c1[x]*d2[y]+c2[y]*d1[x]);
}


其中 当输入一个Xi   c1[Xi]++,d1[Xi]+=i;

VIEW CODE
 
/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
typedef long long LL;
const int inf = 0x3fffffff;
const int mmax = 100010;
LL sum[31][2];
int Size[31];
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int k,x;
    while(cin>>k)
    {
        for(int i=1;i<=k;i++)
        {
            scanf("%d",&Size[i]);
            sum[i][0]=sum[i][1]=0;
            for(int j=1;j<=Size[i];j++)
            {
                scanf("%d",&x);
                sum[i][0]+=x;
                sum[i][1]+=x*j;
            }
        }
        printf("0\n");
        for(int i=2;i<=k;i++)
        {
            LL ans=1LL*Size[i-1]*sum[i][1]+1LL*Size[i]*sum[i-1][1];
            ans+=1LL*(Size[i]+1)*Size[i]/2*sum[i-1][0];
            ans+=1LL*(Size[i-1]+1)*Size[i-1]/2*sum[i][0];
            printf("%lld\n",ans);
        }   
    }
    return 0;
}

G: islands的PhotoGraph

纯模拟,都告诉了对应坐标变换关系了,只要按照题意模写就好了。。。。。不需要任何思维难度
 
VIEW CODE
/*
 * Author:  islands
 * Created Time:  2015/8/13 21:49:13
 * File Name: stand.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 300;
int Photo[mmax][mmax];
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
                scanf("%d",&Photo[i][j]);
        int q;
        cin>>q;
        while(q--)
        {
            int d,k;
            cin>>d>>k;
            if(d==0)
            {
                for(int i=0;i<(n/k);i++)
                {
                    for(int j=0;j<(m/k);j++)
                    {
                        printf("%d%c",Photo[i*k][j*k],(j==(m/k)-1)?'\n':' ');
                    }
                }
            }
            else
            {
                for(int i=0;i<n*k;i++)
                {
                    for(int j=0;j<m*k;j++)
                    {
                        printf("%d%c",Photo[i/k][j/k],(j==m*k-1)?'\n':' ');
                    }
                }
            }
        }
    }
    return 0;
}

H: Crysis 3

求区间内最大值的位置,取最靠左的那个的位置。单点修改。

思路:用线段树维护区间内的最大值和最大值最靠左的位置,记录2个值,一个记录最大值,另一个几率对应位置。实际上只用开一个值,只用记录位子即可,因为位置包含着最大值。

/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <time.h>
#define debug
using namespace std;
const int inf = 0x3fffffff;
const int mmax = 100010;
int A[mmax];
struct node
{
    int l,r;
    int pos,max_val;
    int mid()
    {
        return (l+r)>>1;
    }
}T[4*mmax];
void push_up(node &fa,node &ls,node& rs)
{
    fa.l=ls.l,fa.r=rs.r;
    fa.max_val=max(ls.max_val,rs.max_val);
    if(fa.max_val==ls.max_val)
    {
        fa.pos=ls.pos;
        return ;
    }
    fa.pos=rs.pos;

}
void build(int id,int l,int r)
{
    T[id].l=l,T[id].r=r;
    if(l==r)
    {
        T[id].pos=l;
        T[id].max_val=A[l];
        return ;
    }
    int mid=T[id].mid();
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);
    push_up(T[id],T[id<<1],T[id<<1|1]);
}
void update(int id,int pos,int val)
{
    if(T[id].l==T[id].r)
    {
        T[id].max_val=val;
        return ;
    }
    int mid=T[id].mid();
    if(mid>=pos)
        update(id<<1,pos,val);
    else
        update(id<<1|1,pos,val);
    push_up(T[id],T[id<<1],T[id<<1|1]);
}
node query(int id,int l,int r)
{
    if(l<= T[id].l && T[id].r <=r)
        return T[id];
    int mid=T[id].mid();
    node tmp[3];
    int t=0;
    if(mid>=l)
        tmp[1]=query(id<<1,l,r),t++;
    if(mid<r)
        tmp[2]=query(id<<1|1,l,r),t+=2;
    if(t<3)
        return tmp[t];
    push_up(tmp[0],tmp[1],tmp[2]);
    return tmp[0];
}
int main()
{
    #ifdef debug
    freopen("in.txt","r",stdin);
    #endif
    int t,n,q;
    int op,a,b;
    cin>>t;
    while(t--)
    {
        scanf("%d %d",&n,&q);
        for(int i=1;i<=n;i++)
            scanf("%d",&A[i]);
        build(1,1,n);
        while(q--)
        {
            scanf("%d %d %d",&op,&a,&b);
            if(op==1)
            {
                node ans=query(1,a,b);
                printf("%d\n",ans.pos);
            }
            else
                update(1,a,b);
        }
    }
    return 0;
}


  我想通过这次比赛,大家应该更加清楚怎么的真实水平到底是什么样的。这场比赛不论是题目质量,还是比赛的客观性都比前面的几场都高,毕竟是原创题,很有针对性。希望大家下来以后还好总结暑假培训期间的得失。 

你可能感兴趣的:(算法,ACM)