Chieh's Blog

Because Of Coding

R308 DIV2 D. Vanya and Triangles

飞机票:http://codeforces.com/contest/552/problem/D

题意:给你n个点,两两相连,一共有多少个三角形是已给出n个点中三个点组成的

分析:这题的话O(n3)能过,我不知道出题人是什么意思,,,我的话是n*40000.最多也就O(10*n*n)的复杂度,跑了173ms。直接进入正题:此题可以用计数容斥来做,具体就是对与每个点i 对(i+1~n)进行枚举。(1<=i<=n)具体的做法是:假设当前的点是i,枚举(i+1~n)和i的斜率,并把斜率存入到数组中,因为最大的yj-yi=200 xj-xi=200,所以存入到200*200的数组中就可以了但是这里我们必须把(yj-yi)和(xj-xi)的最大公约数求出来,假设除了最大公约数后分别为p和q,因为这样才可以知道斜率为p/q的点关于i有多少个。这里的话,我们可以预处理下1~200每对的最大公约数,这样就是O(1)的时间求最大公约数了。而且我是用了两个数组,一个存斜率是正数,一个存斜率是负数,而且还得存p=0或q=0的情况。最后就是对三角形的个数进行统计,假设斜率为k的点有有l个,除了l个点和i点后有r个点,则三角形个数就是l*r,因为l上选一个点,i一个点,r上一个点,计算完后,需要把重复的除掉,因为对于两个点a和b,我们计算了a到b和b到a的,所以最后除以2就可以了,然后加到总数上去最后啪啪啪,就可以AC了。

/*
Author:Chieh
Because Of Coding
*/
#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
#define EPS 1e-9
using namespace std;
int x1[201][201];
int y11[201][201];
int G[201][201];
const int maxn=2*1234;
struct he
{
    int x,y;
} Sky[maxn];
int n;
void init()
{
    for(int i=1; i<=n; i++)scanf("%d%d",&Sky[i].x,&Sky[i].y);
}
void play()
{
    LL ans=0;
    for(int i=1; i<=n; i++)
    {
        memset(x1,0,sizeof(x1));
        memset(y11,0,sizeof(y11));
        int l=0;
        int r=0;
        for(int j=i+1; j<=n; j++)
        {
            int q=Sky[i].y-Sky[j].y;
            int p=Sky[i].x-Sky[j].x;

            if(p==0)
            {
                l++;
            }
            else if(q==0)
            {
                r++;
            }
            else
            {
                int nn=G[abs(q)][abs(p)];
                q/=nn;
                p/=nn;
                if(q>0&&p>0)
                {
                    x1[q][p]++;
                }
                else if(q<0&&p<0)
                {
                    x1[-q][-p]++;
                }
                else
                {
                    y11[abs(q)][abs(p)]++;
                }
            }

        }
        LL sum=0;
        int sk=n-i;
        if(l!=0)
        {
            sum+=(sk-l)*l;
        }
        if(r!=0)
        {
            sum+=(sk-r)*r;
        }
        for(int j=1; j<=200; j++)
        {
            for(int k=1; k<=200; k++)
            {
                int p=x1[j][k];
                int q=y11[j][k];
                if(p!=0)
                {
                    sum+=(sk-p)*p;
                }
                if(q!=0)
                {
                    sum+=(sk-q)*q;
                }
            }
        }
        sum/=2;
        ans+=sum;
    }
    printf("%I64d\n",ans);
}
int GCD(int a,int b)
{
    if(a<b)swap(a,b);
    if(a%b!=0)
    {
        return GCD(b,a%b);
    }
    return b;
}
int main()
{
    for(int i=1; i<=200; i++)
    {
        for(int j=1; j<=200; j++)
        {
            G[i][j]=GCD(i,j);
        }
    }

    while(scanf("%d",&n)!=EOF)
    {
        init();
        play();
    }
//    cout << "Hello world!" << endl;
    return 0;
}

R308 DIV2 C. Vanya and Scales

飞机票:http://codeforces.com/contest/552/problem/C

题意:给你w和m,然后用w0.w1.w2......w100,放在天平两侧,且m肯定要放,w可以用完,但是每个只有一个,使得天平平衡。

分析:数学题,我们假设有个数组ai,且长度为0~100,且ai对应w0.w1.w2......w100其中,ai可以为1,0,-1,我们现在的目标就很明确了a0*w0+a1*w1+a2*w2......a100*w100=m如果ai为1的话,就是说指数为i的要放在天平与m相反的盘子里,如果是0,就是这个数不用用,如果是1,就是放在与m相同的地方。然后我们可以提取这些数的最大公约数,则有w0(a0+a1*w1/w0+a2*w2/w0...... )=m

从这个公式我们可以推出,m必须要可以整除w0,因为不能整除就是NO了,然后就剩下(a0+a1*w1/w0+a2*w2/w0...... )=m/w0 ,把a0移过来(a1*w1/w0+a2*w2/w0...... )=m/w0 +a0 则左边可以用前面的方法继续迭代,右边的话a0有三种可能,且如果这三种可能没有一种可以使得右边整除左边的最大公约数,则也是NO,因为w>2,所以这三种可能做多就一种满足。然后当右边=1的时候就结束,因为左边和右边一样了,YES。然后啪啪啪,就可以AC了

/*
Author:Chieh
Because Of Coding
*/
#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
#define EPS 1e-9
using namespace std;
int w,m;
void init(){

}
void play(){
    int t=0;
    while(1){
        if(m==1){
            printf("YES\n");
            return;
        }
        if(m%w!=0){
            if((m+1)%w==0){
                m=m+1;
                m/=w;
            }
            else if((m-1)%w==0){
                m=m-1;
                m/=w;
            }
        }
        else{
            m/=w;
        }
        t++;
        if(t==101){
            printf("NO\n");
            return;
        }
    }
}
int main()
{
    while(scanf("%d%d",&w,&m)!=EOF){
        init();
        play();
    }
//    cout << "Hello world!" << endl;
    return 0;
}

HDU 5269 ZYB loves Xor I

飞机票:http://acm.hdu.edu.cn/showproblem.php?pid=5269

题意:给你一个数组A,A[i]^A[j]之后,假设为P,求lowbit(P)(就是P最低位为1的位置,然后就是2^为1位置)。然后求和,这里的话要计算两遍,即i和j j和i算两个,并不算一个。

分析:起先以为是最低位:不用为2的幂掌握的不够深刻啊。然后百度才知道要幂的。。。这次印象深刻了这种题的话,可以用分治,即分成两个块然后求值。具体怎么求值:

(1)先把所有的数转换为2进制。

(2)从最低位开始,如果是1就放入左边块,如果是0就放入右边块。(这里我是定义vector l,r ,每次是1就存入l,右边就存入r),这样的话就知道l里的元素和r里的元素是异或之后最低位为当前深度的2的幂,然后把l的大小*r的大小*2加到答案里去。*2是因为l和r算一边,然后r和l算遍。

(3)然后把l和r分别进行第二步。因为l和r已经不相干了。这里要及时退出,就是当l和r其中是0时就要退出,不然会TLE

总体复杂的O(n*logn),然后啪啪啪,就可以AC了

/*
Author:Chieh
Because Of Coding
*/
#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
#define EPS 1e-9
using namespace std;
const int maxn=5*12345;
int T;
int n;
int  ai[maxn];
int bi[30];
struct he
{
    bool isok[30];
} Sky[maxn];
int MOD=998244353;
void getBir()
{
    for(int i=1; i<=n; i++)
    {
        memset(Sky[i].isok,0,sizeof(Sky[i].isok));
        for(int j=29; j>=0; j--)
        {
            if(ai[i]-bi[j]>=0)
            {
                ai[i]-=bi[j];
                Sky[i].isok[j]=1;
            }
        }
    }
}
void init()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)scanf("%d",&ai[i]);
    getBir();

}
LL ans;
void DFS(vector<int> V,int depth)
{
    if(depth==30)
    {
        return;
    }
    if(V.size()==0)return;
    vector<int> l;
    vector<int> r;
    for(int i=0; i<V.size(); i++)
    {
        int id=V[i];
        if(Sky[id].isok[depth])
        {
            l.push_back(id);
        }
        else
        {
            r.push_back(id);
        }
    }
    int ls=l.size();
    int rs=r.size();
    LL p=ls*rs;
    ans=(ans+((p*(bi[depth]%MOD)%MOD)*2)%MOD)%MOD;
    DFS(l,depth+1);
    DFS(r,depth+1);
}
int Case;
void play()
{
    ans=0;
    vector<int> V;
    for(int i=1;i<=n;i++)V.push_back(i);
    DFS(V,0);
    printf("Case #%d: %I64d\n",++Case,ans);
}
int main()
{
    bi[0]=1;
    for(int i=1; i<=29; i++){bi[i]=bi[i-1]*2;}
    scanf("%d",&T);
    Case=0;
    while(T--)
    {
        init();
        play();
    }
    // cout << "Hello world!" << endl;
    return 0;
}

 

R307 DIV2 C. GukiZ hates Boxes

飞机票:http://codeforces.com/contest/551/problem/C

题意:有一个数组ai[],有n个元素,ai[i]代表第i个元素的值,有m个学生,学生在最左边,且到第i个元素要i秒,且使第i个元素值减1需要花费1秒,求m个人使所有值为0最少要多少时间,学生可以同时行动。

分析:题目一看,必然二分啊,但是怎么维护这个值呢?这里有2种方法,假设当前判断的值是mid

(1)从后往前推:假设最右边不为0的下标为k,则学生到k需要花费k秒,如果mid<=k直接就不行,因为到了k这个点就没时间了。所以mid肯定要大于k,然后ai[k]就要减掉mid-k的值,这样就是最优的,因为学生的时间没有一秒浪费。

(2)从前往后推:这个情况有点难理解,只能YY了,假设当前的人在i,且后面有值的地方在j(i<j),如果当前这个人再拿一个i的值,他到不了j或则到了j已经没有时间了,这样就有时间浪费了,但是我们会发现,下个人他就多了1秒的时间,就是他如果能到j,则他可以多拿一个,并不会影响到最优值。有点难理解哇~

我的代码是用第二种方法的,复杂度为O(log2INF*n)INF=2*1e18,要及时跳出循环,不然就TLE了,啪啪啪,就可以AC了

/*
Author:Chieh
Because Of Coding
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <stack>
#include <queue>
#define LL long long
#define INF 1e20
#define EPS 1e-9
using namespace std;
const int maxn=123456;
LL ai[maxn];
LL bi[maxn];
int n,m;
void init()
{

    for(int i=1; i<=n; i++)
    {
        scanf("%I64d",&ai[i]);
        bi[i]=ai[i];

    }
}
void play()
{
    LL l=0;
    LL r=INF;
    LL ans=r;
    LL p=INF;
    for(int i=n; i>=1; i--)
    {
        if(ai[i]>0)
        {
            p=i;
            break;
        }
    }
    if(p==INF)
    {
        printf("0\n");
        return;
    }
    n=p;
    while(l<=r)
    {
        LL mid=(l+r)/2;
        int st=1;
        for(int i=1; i<=m; i++)
        {
            LL t=mid;
            int ti=st;
            for(int j=st; j<=n; j++)
            {
                LL now=ai[j]+ti;
                if(now>t)
                {
                    st=j;
                    t-=ti;
                    if(t>0)
                    {
                        ai[j]-=t;
                    }
                    break;
                }
                else
                {
                    t-=now;
                    ai[j]=0;
                    ti=1;
                    st=j;
                }
            }
            if(st==n&&ai[n]<=0)break;
        }
        bool flag=0;
        if(st==n&&ai[n]<=0)flag=1;
        LL p=ai[n];
        for(int i=1; i<=n; i++)ai[i]=bi[i];
        if(!flag)
        {
            l=mid+1;
        }
        else
        {
            r=mid-1;
            ans=mid;
        }
    }
    printf("%I64d\n",ans);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        play();
    }
    //  cout << "Hello world!" << endl;
    return 0;
}

R307 DIV2 B. ZgukistringZ

飞机票:http://codeforces.com/contest/551/problem/B

题意:给你三个字符串,a,b,c,只包含小写字母,求字符串a重新排列后k包含最多的不重叠字串,为b或则为c.

分析:先想想先重组b或则c,会发现这个贪心不正确,因为可能重组一次b后,后面先重组c会比较优然后我就卡题了,一点感觉都没有,而且还分三种情况先b再c,或c再b,或则两个一起来,后面才发现这种贪心明显是错的,因为不是最优的。一直wa test12后来看了下字符串长度为1e5,可以暴力直接过。o(︶︿︶)o ,弱死了,想了半天,至于暴力,就是先预处理出都重组b的情况,假设为sum1,然后枚举sum1,推出重组c的情况sum2,求sum1+sum2的最大值,然后输出,复杂度0(sum1*26)sum1<=100000,所以还是非常快的,啪啪啪,就可以AC了

/*
Author:Chieh
Because Of Coding
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <stack>
#include <queue>
#define LL long long
#define INF 1e9
#define EPS 1e-9
using namespace std;
const int maxn=123456;
char ch1[maxn];
char ch2[maxn];
char ch3[maxn];
int ai[27];
int bi[27];
int ci[27];
int len1,len2,len3;
void init()
{
    len1=strlen(ch1+1);
    len2=strlen(ch2+1);
    len3=strlen(ch3+1);
    memset(ai,0,sizeof(ai));
    memset(bi,0,sizeof(bi));
    memset(ci,0,sizeof(ci));
    for(int i=1; i<=len1; i++)
    {
        int t=ch1[i]-'a'+1;
        ai[t]++;

    }
    for(int i=1; i<=len2; i++)
    {
        int t=ch2[i]-'a'+1;
        bi[t]++;
    }
    for(int i=1; i<=len3; i++)
    {
        int t=ch3[i]-'a'+1;
        ci[t]++;

    }
}
void play()
{

    int sum1=1000000000;
    for(int i=1; i<=26; i++)
    {
        if(bi[i]>0)
        {
            sum1=min(sum1,ai[i]/bi[i]);
        }
    }
    int l=-1,r=-1;
    for(int i=0; i<=sum1; i++)
    {
        for(int j=1; j<=26; j++)
        {
            ai[j]-=(bi[j]*i);
        }
        int sum2=1000000000;
        for(int j=1; j<=26; j++)
        {
            if(ci[j]>0)
            {
                sum2=min(sum2,ai[j]/ci[j]);
            }
        }
        for(int j=1; j<=26; j++)
        {
            ai[j]+=(bi[j]*i);
        }
        if(l==-1&&r==-1)
        {
            l=i;
            r=sum2;
        }
        else
        {
            if(l+r<i+sum2)
            {
                l=i;
                r=sum2;
            }
        }
    }
    for(int i=1; i<=l; i++)printf("%s",ch2+1);
    for(int i=1; i<=r; i++)printf("%s",ch3+1);
    for(int i=1; i<=l; i++)
    {
        for(int j=1; j<=26; j++)
        {
            ai[j]-=bi[j];
        }
    }
    for(int i=1; i<=r; i++)
    {
        for(int j=1; j<=26; j++)
        {
            ai[j]-=ci[j];
        }
    }
    for(int i=1; i<=26; i++)
    {
        for(int j=1; j<=ai[i];j++)printf("%c",'a'+i-1);
    }
    printf("\n");

}
int main()
{
    while(scanf("%s%s%s",ch1+1,ch2+1,ch3+1)!=EOF)
    {
        init();
        play();
    }
    //  cout << "Hello world!" << endl;
    return 0;
}

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;
}

UVALive - 4850 Installations

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

题意:给你n个服务,然后每个服务有时间di,和执行时间si,如果执行后的时间超过di,则惩罚值为max(0,最终的时间-di),求最大两个惩罚值相加最小。

分析:二分套二分套二分,复杂度logn*n*logn*n。。。ORZ

第一个二分是确定两个和最小值用的,假设当前检测的值是x,则我们枚举每个服务,然后第二个二分,二分x的值,且l要大于等于x/2向上取整,r为x,这个是为了确定两个其中一个比较大的值,那么另一个值就可以为x-大的值,然后第三个二分,用来确定k应该插入到哪里去,假设大的值为i,小的值为j,则每个服务的d值都得家j,但是k的d值要加i,应该能理解,然后不是属于k的我们只要排序一次就够了,因为加了跟没加排序不变,然后用第三个二分把k插进去,按更新后的d从小到大,why??因为d越大的话,它的可用区域越大,贪心~所以把小的先处理,如果当前执行的任务超过了当前的d,如果是k,那就是说明k更新的值太小,所以第二个二分的l=mid+1,如果不是k,且超过了,那个r=mid-1,k太大~,如果可以,那么就说明当前方案是可以的,更新第一个二分。。。最后输出,over,啪啪啪,AC~

/*
Author:Chieh
Because of You
*/
#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=5*123;
struct he
{
    int s,d;
} JLJ[maxn];
int T,n;
bool cmp(he a,he b)
{
    return a.d<b.d;
}
void init()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)scanf("%d%d",&JLJ[i].s,&JLJ[i].d);
}
vector<he> V;
int isOK(int x,int y,int idx)
{
    int l=0;
    int r=V.size()-1;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(V[mid].d+y>JLJ[idx].d+x)
        {
            r=mid-1;
        }
        else
        {
            l=mid+1;
        }
    }
    V.insert(V.begin()+l, JLJ[idx]);
    int ip=l;
    int before=0;
    for(int i=0; i<V.size()-1; i++)
    {
        int tm=y;
        if(i==ip)tm=x;
        int s=V[i].s;
        int d=V[i].d+tm;
        if(s+before>d)
        {
            V.erase(V.begin()+ip);
            if(i==ip)return 1;
            return 2;
        }
        else before+=s;
    }
    V.erase(V.begin()+ip);
    return 0;
}
bool check(int x)
{

    for(int i=1; i<=n; i++)
    {
        V.erase(V.begin()+i-1);
        int l=x/2;
        int r=x;
        if(x%2!=0)l++;
        while(l<=r)
        {
            int mid=(l+r)/2;
            int t=isOK(mid,x-mid,i);
            if(t==0)
            {
                V.insert(V.begin()+i-1,JLJ[i]);
                return 1;
            }
            if(t==1)
            {
                l=mid+1;
            }
            else
            {
                r=mid-1;
            }
        }
        V.insert(V.begin()+i-1,JLJ[i]);
    }
    return 0;
}
void play()
{
    sort(JLJ+1,JLJ+1+n,cmp);
    V.clear();
    for(int j=1; j<=n; j++)
    {
        V.push_back(JLJ[j]);
    }
    he a;
    a.s=0;
    a.d=2000000000;
    V.push_back(a);
    int l=0;
    int r=1000000;
    int MIN=1000000000;
    while(l<=r)
    {
        int mid = (l + r) / 2;
        if(check(mid))
        {
            MIN=min(mid,MIN);
            r=mid-1;
        }
        else
        {
            l=mid+1;
        }
    }
    printf("%d\n",MIN);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        play();
    }
    // cout << "Hello world!" << endl;
    return 0;
}

UVALive - 4725 Airport

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

题意:就是给你两个轨道,分别为W,E然后有n个时刻,每个时刻回来pi和qi个飞机分别到W,E,且每个时刻能从W,E中起飞一辆飞机,但是刚飞到的飞机不能起飞。且每个机场当前的飞机编号为0~飞机数量-1。求两个机场一起最大编号的最小值。

分析:我的想法是二分+贪心+树状数组。

二分是为了求最小值,贪心和树状数组主要是判断用的。二分的边界就不用说了,我是直接用了l=1,r=100000,因为每个机场飞机最多的时候就只有100000,不过答案求出来要减一个1才行,因为是0开始的开始进入主题了;

假设当前判断的速度是x,则我们循环累计每个机场的数量,且每次存入树状数组(后面logn有用)。假设W机场每个时刻飞来ai两飞机。则当前W机场有sum(1。。。k);假设我sum[0],如果sum[0]>x了,那就说明前面的飞机必须起飞need=sum[0]-x辆了,然后就用到了贪心思想:因为每个时刻只能起飞一辆飞机,所以我们从1开始,枚举。。。而且我们用一个vis数组记录当前是否有飞机起飞过,有的话,就继续向后。当当前时间是m时。则我们可以用树状数组求的前面的还有没有飞机,如果有,我们就可以起飞,然后树状树状数组减1、、、如果当m=k的时候还不行说明方案不可行,直接返回false,如果可以那么继续循环,一直到最后。。。如果可以则返回true 更新最小值、、、这里哪里用了贪心呢,就是sum那里大于x时,因为我们从前面开始取的话,这样后面大于x时的选择空间更大。。。这样就可以了,复杂度为log(100000)*(n+2*n*logn)就是logn^2*n了吧,速度还是ok的,啪啪啪就可以AC了

/*
Author:Chieh
Because of You
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#define LL long long
using namespace std;
const int maxn=5*1234;
int C[2][maxn];
int lowbit(int x)
{
    return x&(-x);
}
int sum(int n,int idx)
{
    int sum=0;
    while(n>0)
    {
        sum+=C[idx][n];
        n=n-lowbit(n);
    }
    return sum;
}
int n,T;
void change(int i,int x,int idx)
{
    while(i<=n)
    {
        C[idx][i]=C[idx][i]+x;
        i=i+lowbit(i);
    }
}

struct he
{
    int a,b;
} JLJ[maxn];
void init()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d",&JLJ[i].a,&JLJ[i].b);
    }
}
bool vis[maxn];
int summ[2];
int s[2];
bool check(int x)
{
    memset(vis,0,sizeof(vis));
    memset(C[0],0,sizeof(C[0]));
    memset(C[1],0,sizeof(C[1]));
    summ[0]=summ[1]=0;
    s[0]=s[1]=1;
    for(int i=1; i<=n; i++)
    {
        for(int j=0; j<=1; j++)
        {
            int tp=JLJ[i].a;
            if(j==1)
            {
                tp=JLJ[i].b;
            }
            summ[j]+=tp;
            if(summ[j]>x)
            {
                int need=summ[j]-x;
                int st=1;
                while(st<=need)
                {
                    if(s[j]==i)return 0;
                    if(vis[s[j]])
                    {
                        s[j]++;
                    }
                    else
                    {
                        int p=sum(s[j],j);
                        if(p>0)
                        {
                            change(s[j],-1,j);
                            st++;
                            vis[s[j]]=1;
                            s[j]++;
                        }
                        else
                        {
                            s[j]++;
                        }
                    }
                }
                summ[j]=x;
            }
            change(i,tp,j);
        }
    }
    return 1;
}
void play()
{
    int l=1,r=100000;
    int MIN=r;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(check(mid))
        {
            MIN=min(MIN,mid);
            r=mid-1;
        }
        else
        {
            l=mid+1;
        }

    }
    printf("%d\n",MIN-1);
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        init();
        play();
    }
    // cout << "Hello world!" << endl;
    return 0;
}