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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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<

B: 奔跑的Fm

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

kruskal代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 costq;
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>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=2)
            puts("Don't touch me!");
        else
            cout<<"totlen: "<

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
#include
#include
#include
#include
#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;i1)
        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
#include
#include
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

匹配做法:
/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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];
mapq;
int link[mmax];
bool vis[mmax];
bool match(int x,int n)
{
    for(int i=0;i>n>>m)
    {
        memset(G,0,sizeof G);
        q.clear();
        for(int i=0;i=1;i--)
        {
            if(!q.count(i) && cnt

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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<

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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>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

H: Crysis 3

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

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

/*
 * Author:  islands
 * Created Time:  2015/8/14 14:38:28
 * File Name: stand.cpp
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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>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之路)