Chieh's Blog

Because Of Coding

UVA - 11174 Stand in a Line

飞机票: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=34531

题意:就是给你n个人,m种关系a b,b是a的父亲,且父亲只有一个,而且自己不可能是自己的祖先,求父亲在儿子前面的方案数取模

分析:题意稍微想下其实是一堆树,我们可以先把无根转换为有根,就是把森林转换成树,我们可以把没有父亲的借点全部连在0这个节点上,这样就是一棵树。然后怎么求解呢。假设当前的节点fa,它有3个儿子,且每个儿子那一块分别有i1,i2,i3个人,而且每个的方案数分别为j1,j2,j3。。。那我们可以知道除了当前的父节点一共有i1+i2+i3个人,假设为sump,当前的父节点肯定要在他们前面,所以不用计算,位置固定,然后下面三个子树可以随机组合,就是说我们可以有C(sump,i1)种方案来放置i1中的人,但是这里还不够完整,因为这个还没有排列好,就是说i1中的人顺序应该怎么样,因为我们知道j1,所以只要两个相乘就可以,why?因为选i1的方案数就是选取了i1个位置,然后他们的方案共有j1,所以两个相乘就可以,就是C(sump,i1)*j1,那么i2就不是这样求了,因为sump减少了i1个人,所以只有sump-i1个人了所以是C(sump-i1,i2)*j2,那么第三个就是C(sump-i1-i2,i3)*j3,这样方案就是了,然后将三个相乘就是当前的方案数了。记得取模不是一般的取模,因为带有除法,但是有模版,接下来我要证明无论顺序怎么样都没有问题:C(sump,i1)*C(sump-i1,,i2)=C(sump,i2)*C(sump-i2,i1)其实很简单,左边展开和右边张开通分就可以然后啪啪啪就可以AC了~

/*
Author:Chieh
Because Of Coding
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#define INF 1e9
#define EPS 1e-9
#define LL long long
#define MOD 1000000007
using namespace std;
const int maxn=12345*4;
vector<int> V[maxn];
int n,m;
int fa[maxn];
void init()
{
    memset(fa,0,sizeof(fa));
    scanf("%d%d",&n,&m);
    V[0].clear();
    for(int i=1; i<=n; i++)
    {
        V[i].clear();
    }
    int u,v;
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d",&u,&v);
        fa[u]=v;
    }
    for(int i=1; i<=n; i++)
    {
        u=fa[i];
        V[u].push_back(i);
    }
}
LL exp_mod(LL a, LL b, LL p)
{
    LL res = 1;
    while(b != 0)
    {
        if(b&1) res = (res * a) % p;
        a = (a*a) % p;
        b >>= 1;
    }
    return res;
}

LL Comb(LL a, LL b, LL p)
{
    if(a < b)   return 0;
    if(a == b)  return 1;
    if(b > a - b)   b = a - b;

    LL ans = 1, ca = 1, cb = 1;
    for(LL i = 0; i < b; ++i)
    {
        ca = (ca * (a - i))%p;
        cb = (cb * (b - i))%p;
    }
    ans = (ca*exp_mod(cb, p - 2, p)) % p;
    return ans;
}

LL Lucas(int n, int m, int p)
{
    LL ans = 1;

    while(n&&m&&ans)
    {
        ans = (ans*Comb(n%p, m%p, p)) % p;
        n /= p;
        m /= p;
    }
    return ans;
}

pair<int,LL> DFS(int now)
{

    int sum=0;
    LL ans=1;
    vector<pair<int,LL> > S;
    for(int i=0; i<V[now].size(); i++)
    {
        int v=V[now][i];
        pair<int,LL> p=DFS(v);
        sum+=p.first;
        S.push_back(p);
    }
    int tm=sum;
    for(int i=0; i<S.size(); i++)
    {
        int a1=S[i].first;
        LL a2=S[i].second;
        LL pq=Lucas(tm,a1,MOD);
        ans=((pq*a2)%MOD)*ans%MOD;
        tm-=a1;
    }
    return make_pair(sum+1,ans);
}
void play()
{
    pair<int,LL> p=DFS(0);
    printf("%lld\n",p.second);
}
int T;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        play();
    }
//   cout << "Hello world!" << endl;
    return 0;
}

 

 

 

UVA - 11552 Fewest Flops

飞机票:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=28550

题意:给你一个字符串,假设长度为len,分割成len/k个字符串,假设一共有n个,每个被分割的部分可以随便重组,然后将n个字符串按1~n顺序排列,求最少的块,块定义为连接的是相同的字母。

分析:DP 复杂度n*26*26

起先我们可以将n个字串进行预处理,记录每个字母在当前串是否存在,不需要记录多少个(因为我们贪心的想法,肯定会把同一个串里同一个字母合在一起,这样才最优),然后我们记录长度,因为长度为1的要特殊处理,假设存在数组为ai,长度数组为bi,然后我们dp,假设当前处理的是i,如果bi[i]不等于1,那么我们枚举i里面的字母,如果当前字母是j,如果i-1也存在j,那么我们可以把i-1里面不是用j放第一的那种方案拿过来然后加上当前的bi[i]-1和我们当前的方案比一下,取最小的,因为合并,所以少了1个,假设我们定义数组islast记录把字母放在最后的最优解,计算好后,我们就知道把j放在当前的第一最优解是多少了,然后枚举其他不是j的,更新把他们放在最后的最优解。。。结束后,我们要更新最优解,因为最优解加上bi[i+1]就是i+1不管怎么排的下限、、、

这里我只处理了bi!=1,如果等于1,就好办了,直接把当前的最优和i-1存在j的比一下就好了,相当于直接把j扔到i-1里去了具体DP思想看代码。。。啪啪啪,就可以AC了

/*
Author:Chieh
Because of You
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <stack>
#include <vector>
#include <queue>
#define LL long long
#define INF 1e9
using namespace std;
const int maxn=1234;
int k,T;
char ch[maxn];
bool ai[maxn][27];
int islast[maxn][27];
int bi[maxn];
void init()
{
    scanf("%d%s",&k,ch+1);
}
void play()
{
    memset(ai,0,sizeof(ai));
    memset(bi,0,sizeof(bi));
    int len=strlen(ch+1);
    int n=len/k;
    for(int i=0; i<=n; i++)
        for(int j=1; j<=26; j++)islast[i][j]=INF;
    for(int i=1; i<=n; i++)
    {
        int st=(i-1)*k+1;
        int en=i*k;
        for(int j=st; j<=en; j++)
        {
            int p=ch[j]-'a'+1;
            if(!ai[i][p])bi[i]++;
            ai[i][p]=1;
        }
    }
    int before=0;
    for(int i=1; i<=n; i++)
    {
        before+=bi[i];
        int now=before;
        if(bi[i]==1)
        {
            for(int j=1; j<=26; j++)
            {
                if(ai[i][j])
                {
                    islast[i][j]=min(before,islast[i-1][j]);
                    now=islast[i][j];
                    break;
                }
            }

        }
        else
        {
            for(int j=1; j<=26; j++)
            {
                if(!ai[i][j])continue;
                int tm=min(islast[i-1][j]-1+bi[i],before);
                for(int k=1; k<=26; k++)
                {
                    if(ai[i][k]&&k!=j)
                    {
                        islast[i][k]=min(islast[i][k],tm);
                    }
                }
                now=min(tm,now);
            }
        }
        before=now;
    }
    int MIN=INF;
    for(int i=1; i<=26; i++)
    {
        MIN=min(islast[n][i],MIN);
    }
    printf("%d\n",MIN);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        play();
    }
//   cout << "Hello world!" << endl;
    return 0;
}

UVA - 10534 Wavio Sequence

飞机票:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=19219

题意:给你一个长度为n的序列,求最长的奇数长度,使前k+1严格递增,后k+1个严格递减

分析:两次LIS 复杂度(n*logn+n*logn)=n*logn

先根据LIS求出1~n每个位置的最长递增序列长度,这里从1至n计算,然后求n~1各位置的最长序列长度,这里从n至1计算,为了不增加编码长度,可以把序列倒转,然后再求一遍,然后比较每个位置左边和右边最长序列的最小值,然后乘以2-1,每次更新MAX,最后的MAX就是最长的符合要求的长度了,这里我用了bi[0]和bi[1]两个数组,其中bi[0]是从1~n的,bi[1]是从n~1的,那么每次更新的话,就是bi[i]和bi[n-i+1](原始位置的右边)然后取最小,MIN*2-1即可,更新MAX,OK了~啪啪啪。就可以AC了

/*
Author:Chieh
Because of You
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
const int maxn=12345;
int ai[maxn],ans[maxn],n,bi[2][maxn],sw[maxn];
int len;
void init()
{
    for(int i=1; i<=n; i++)scanf("%d",&ai[i]);
}
int brsearch(int x)
{
    int left=0,right=len;
    while(left<right)
    {
        int   mid = left+(right-left)/2;
        if(ans[mid]>=x) right=mid;
        else left=mid+1;
    }
    return left;
}
void CalLIS(int status)
{
    ans[1] = ai[1];
    bi[status][1]=1;
    len=1;
    for(int i=2; i<=n; i++)
    {
        if(ai[i]>ans[len])
        {
            ans[++len]=ai[i];
            bi[status][i]=len;
        }
        else
        {
            int pos=brsearch(ai[i]);
            ans[pos] = ai[i];
            bi[status][i]=pos;
        }
    }
}
void play()
{
    CalLIS(0);
    for(int i=1; i<=n; i++)
        sw[i]=ai[n-i+1];
    for(int i=1; i<=n; i++)
        ai[i]=sw[i];
    CalLIS(1);
    int MAX=0;
    for(int i=1; i<=n; i++)
    {
        int p=min(bi[0][i],bi[1][n-i+1]);
        MAX=max(p*2-1,MAX);
    }
    printf("%d\n",MAX);

}
int main()
{

    while(scanf("%d",&n)!=EOF)
    {
        init();
        play();
    }
    return 0;
}

UVA - 10795 A Different Task

飞机票:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=34699

题意:就是给你初始的汉诺塔大小升序,和目标的汉诺塔大小升序,求最小的移动步数;

分析:刚开始没有想法,后来想到了万法归宗,就是已知初始状态变回原始状态也是可行的就是说假如A,B,C三个分别有编号1,2,3的盘子,我们可以变回A,B,C只有A上有3个盘子的状态好复杂,所以我是用了反推的方法,先设A,B,C有3个盘子,然后推出A,B,C分别有1个盘子的步数为几步。。所以加个判断,加入当前盘子是A,当前要移动的最大盘是k,要移动到B,则上面的k-1个盘都要移动到C才行,就是2^(k-1)步,这里不用减1,因为k要移一步。这样先把初始的都先归到一个盘子一共要几步,然后根据当前的盘要移动到哪进行计算步数就ok了,

解释下吧:

对于第一个样例,我们可以把第3个盘提出来,则1,2个盘回到C上去,这样一共要3步+第三个盘要1步,就是4步,然后1,2都在C上,当要把2移到B上时,我们得把2上面的移到A上,则要1步+第二个盘要1步,就是2步,最后再加上第一个盘移一次就是答案了,7;

啪啪啪~就可以AC了

/*
Author:Chieh
Grow up happy
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
#include <stack>
#define LL long long
using namespace std;
const int maxn=67;
struct he
{
    int s,e;
} JLJ[maxn];
int n;
void init()
{
    for(int i=1; i<=n; i++)scanf("%d",&JLJ[i].s);
    for(int i=1; i<=n; i++)scanf("%d",&JLJ[i].e);
}
void play()
{
    int st=-1;
    for(int i=n; i>=1; i--)
    {
        if(JLJ[i].s!=JLJ[i].e)
        {
            st=i;
            break;
        }
    }
    if(st==-1)
    {
        printf("0\n");
        return;
    }
    LL sum=0;
    int now=6-JLJ[st].s-JLJ[st].e;
    for(int i=st-1; i>=1; i--)
    {
        if(JLJ[i].s==now)
            continue;
        else
        {

            sum+=((1LL)<<(i-1));
            now=6-JLJ[i].s-now;
        }
    }
    sum++;
    now=6-JLJ[st].s-JLJ[st].e;
    for(int i=st-1; i>=1; i--)
    {
        if(JLJ[i].e==now)continue;
        else
        {
            sum+=((1LL)<<(i-1));
            now=6-JLJ[i].e-now;
        }
    }
    printf("%lld\n",sum);
}
int main()
{
    int Ca=1;
    while(scanf("%d",&n)!=EOF)
    {   if(n==0)break;
        init();
        printf("Case %d: ",Ca++);
        play();
    }
    return 0;
}

UVA - 11464 Even Parity

飞机票: http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=24665

题意:就是说给你个矩阵,你把0变为1,使矩阵中各个点的上下左右相加为偶数(如果上下左右存在的话),求改变最少的0的数量

分析:如果枚举每个点的话肯定就TLE了,但是我们可以只枚举第一行就行。。。这样就可以推出下面所有的行,因为下面的行可以根据当前行的上左右推出来,复杂度为2^n*(n^2),可以接受~,啪啪啪,AC啦

/*
Author:Chieh
Grow up happy
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
#include <stack>
#define LL long long
using namespace std;
const int maxn=20;
int T,n;
int ai[maxn][maxn];
int bi[maxn][maxn];
void init()
{
    scanf("%d",&n);
    memset(ai,0,sizeof(ai));
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            scanf("%d",&ai[i][j]);
        }
    }
}
int MIN;
void DFS(int depth,int sum)
{

    if(depth>n)
    {
        int now=sum;
        memset(bi,0,sizeof(bi));
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                bi[i][j]=ai[i][j];
            }
        }
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                int tm=bi[i-1][j]+bi[i][j-1]+bi[i][j+1];
                if(tm%2==0)
                {
                    if(bi[i+1][j]==1)return;
                    else continue;
                }
                else
                {
                    if(bi[i+1][j]==0&&i+1<=n)
                    {
                        bi[i+1][j]=1;
                        now++;
                    }
                    else if(bi[i+1][j]==1)continue;
                    else
                    {
                        return;
                    }
                }
            }
        }
        MIN=min(now,MIN);
        return;

    }
    DFS(depth+1,sum);
    if(ai[1][depth]==0)
    {
        ai[1][depth]=1;
        DFS(depth+1,sum+1);
        ai[1][depth]=0;
    }

}
void play(int Case)
{
    MIN=1000000000;
    DFS(1,0);
    if(MIN==1000000000)MIN=-1;
    printf("Case %d: %d\n",Case,MIN);
}
int Case;
int main()
{   Case=1;
    scanf("%d",&T);
    while(T--)
    {
        init();
        play(Case);
        Case++;
    }
    //cout << "Hello world!" << endl;
    return 0;
}