Codeforces Round #277 (Div. 2) A,B,C,D,E

A. Calculating Function

题目链接:http://codeforces.com/problemset/problem/486/A

题意:给出n,让你计算f(n) 

f(n) =  - 1 + 2 - 3 + .. + ( - 1)nn

思路:刚开始看到的时候想着一个for循环过去 但是发现肯定会超时啊 。

发现n如果是偶数答案就是n/2,n如果是奇数就可以先化成偶数再把最后一项加上((n-1)/2-n).

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;

int main()
{
    long long n,res;
    cin>>n;
    if(n%2==0) res=n/2;
    else{
        res=(n-1)/2-n;
    }
    cout<<res<<endl;
}

B. OR in Matrix

题目链接:http://codeforces.com/problemset/problem/486/B

题意:给出一个矩阵的运算,让你已知运算结果去推原矩阵。

思路:找规律的题目,如果不能直接找到规律的话可以先写一个正向运算的程序,之后会发现如果Aij=1,那么B的i行上的所有数和j列上的所有数必然都为1,由此条件就可以由B来计算出A了。

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;

const int maxn=150;

int A[maxn][maxn];
int C[maxn][maxn];
int B[maxn][maxn];

int main()
{
    int n,m,mid;
    bool flag;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) for(int j=0;j<m;j++) scanf("%d",&B[i][j]);
    memset(A,0,sizeof(A));
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            mid=0;
            for(int k=0;k<m;k++) mid+=B[i][k];
            for(int k=0;k<n;k++) mid+=B[k][j];
            if(mid==n+m) A[i][j]=1;
        }
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            C[i][j]=0;
            for(int k=0;k<m;k++) C[i][j]|=A[i][k];
            for(int k=0;k<n;k++) C[i][j]|=A[k][j];
        }
    }
    flag=1;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(C[i][j]!=B[i][j]){
                flag=0;
                break;
            }
        }
        if(flag==0) break;
    }
    if(!flag) printf("NO\n");
    else{
        printf("YES\n");
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++) printf("%d%c",A[i][j],j==m-1? '\n':' ');
        }
    }
    return 0;
}


C. Palindrome Transformation

题目链接:http://codeforces.com/problemset/problem/486/C

题意:给出一个字符串和光标的起始位置,再给出以下4种操作 光标左移 光标右移 字符上换 字符下换。 问将给出的字符换成回文串的最小花费是多少。

思路:因为字符可以向上换也可以向下换,所以更换字符所消耗的花费是一定的,问题是如何安排光标的的路线。显而易见,我们在更换字符的时候肯定是确定左段字符(对称中心的左边)或者右段字符(对称位置的右边)不动,调整其中的一段,我们可以假设都是调整的左段,记录左段需要调整的位置的最左端和最右端,求出给出光标a关于对称中心的对称点b,最短路径一定是先到左右端点的其中一个再从其中左端点或右端点到另一个端点,之后再枚举起点a,b取最小值即可。

ps:固定取左段是因为给出的位置是一定的,我们可以假设位置是不固定的,所以枚举a和b,就相当于给出位置枚举左段和右段。

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cmath>
#define INF 1000000000

using namespace std;

const int maxn=100500;

char cc[maxn];
int a[maxn];
int m1,m2,rmin,rmax,res;
int len;
int pp,nn,mp;

int getdis(int pp)
{
    int ans=0;
    ans=min(abs(pp-rmin),abs(pp-rmax));
    ans+=(rmax-rmin);
    return ans;
}

int main()
{
    scanf("%d%d",&nn,&pp);
    scanf("%s",cc);
    len=nn;
    memset(a,0,sizeof(a));
    rmin=INF;
    res=INF;
    rmax=-1;
    for(int i=0;i<len/2;i++){
        m1=max(cc[i],cc[len-1-i]);
        m2=min(cc[i],cc[len-1-i]);
        a[i]=min(m1-m2,m2+26-m1);
        if(a[i]!=0){
            rmax=max(rmax,i);
            rmin=min(rmin,i);
        }
    }
    //for(int i=0;i<len/2;i++) printf("a[%d]=%d..\n",i,a[i]);
    pp--;
    mp=max(pp,len-1-pp);
    pp=min(pp,len-1-pp);
    if(rmin==INF){
        printf("0\n");
        return 0;
    }
    //printf("pp=%d mp=%d.\n",pp,mp);
    res=min(getdis(pp),getdis(mp));
    for(int i=0;i<len/2;i++) res+=a[i];
    printf("%d\n",res);
    return 0;
}

D. Valid Sets

题目链接:http://codeforces.com/problemset/problem/486/D

题意:给出一棵树和树上每点的权值,问存在多少联通的子集(保证子集中的最大权值-最小权值<=d)。

思路:枚举根节点,以被枚举的节点作为整棵树权值的最大值(关键)和根节点的子树的个数,这个在计算的时候有一点动态规划的思想。设f(x)表示的是x节点这棵树中满足条件的子树的个数,假设a节点有b,c,d三个儿子则有f(a)=(f(b)+1)*(f(c)+1)*(f(d)+1).其中(+1)表示的是不选任何可行解的情况,这样的话就能在O(n^2)的复杂度下求出结果了。

反思:在比赛的时候没做出来这道题目,感觉还是树形dp太弱,想不出来该怎么转移,解法中的枚举最大值作为根其实就是将问题进一步简化为其后的转移提供条件~ 吸取教训呀。

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

typedef long long LL;

const int maxn=2050;
const LL mo=1000000007;

vector<int> P[maxn];
int val[maxn];
int d,n;
int s,t;
bool flag[maxn];

LL dfs(int u,int fa,int root)
{
    LL res=1;
    for(int i=0;i<P[u].size();i++){
        int v=P[u][i];
        if(v==fa) continue;
        if(val[v]>val[root]) continue;
        if(val[root]-val[v]>d) continue;
        if(val[root]==val[v]&&flag[v]) continue;
        res=(res*(dfs(v,u,root)+1))%mo;
    }
    return res;
}

int main()
{
    memset(flag,0,sizeof(flag));
    scanf("%d%d",&d,&n);
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=1;i<n;i++){
        scanf("%d%d",&s,&t);
        P[s].push_back(t);
        P[t].push_back(s);
    }
    LL ans=0;
    for(int i=1;i<=n;i++){
        ans=(ans+dfs(i,-1,i))%mo;
        flag[i]=1;
    }
    printf("%I64d\n",ans);
    return 0;
}

E. LIS of Sequence

题目链接:http://codeforces.com/problemset/problem/486/E

题意:根据最长上升子序列将数列中的数进行分类。

思路:挑战上面关于LIS有一个O(n^2)的做法,和一个O(nlogn)的做法,O(nlogn)的那个做法不知道怎么套到这道题上面来用。我用的是O(n^2)的做法然后用线段树优化成了O(nlogn)。

定义dp[i]表示从0~i这段数中以i结尾的最长上升子序列的长度,所以有dp[i]=max(dp[i],dp[j]+1(j<i&&aj<ai)) 我们可以用线段树查找和维护区间最大值的方法来logn的找到这个dp[j]具体方法看代码吧。

再定义rev_dp[i]表示从i~n-1这段数种以i开始的最长上升子序列的长度,在求dp[i]的时候我们已经求出来了最长上升子序列的长度为rmax。 则我们可以得到如下的一个性质

dp[i]+rev_dp[i]-1=rmax <=> i位置上的这个数是在最长上升子序列上

因为转移的时候都是+1的转移,所以最长上升子序列上的dp值都是连续的,所以如果我们发现某个dp值只有一个位置在最长上升子序列上,则这个位置的数就应该在每个最长上升子序列上。由此我们就能对数进行分类了。

(对于rev_dp[i]只要反着向dp[i]那样算一次即可)

ps:这道题我还是想了很久的,dp[i]的线段树优化求法很快就想到了,怎么确定位置我是走了好多弯路撞了好几次墙才想出来的,中间想过记录转移状态呀,加vector呀等等的想法,最后都被pass掉了。

哎 还是太弱了呀~

code:

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;


const int maxn=100500;

struct sg_tree
{
    int l,r;
    int dat;
} P[maxn*4+20];

int n;
int a[maxn];
int dp[maxn],rev_dp[maxn],cnt[maxn];
int ans[maxn];

void Build(int k,int l,int r)
{
    P[k].l=l;
    P[k].r=r;
    P[k].dat=0;
    if(l==r) return ;
    Build(2*k+1,l,(l+r)/2);
    Build(2*k+2,(l+r)/2+1,r);
}

void init(int nn)
{
    n=1;
    while(n<nn) n*=2;
    Build(0,0,n-1);
}

void update(int k,int a)
{
    k+=n-1;
    P[k].dat=a;
    while(k>0){
        k=(k-1)/2;          //因为线段树的维护的区间从0开始
        P[k].dat=max(P[(k<<1)+1].dat,P[(k<<1)+2].dat);
    }
}

int query(int a,int b,int k)
{
    if(P[k].l>b||P[k].r<a) return 0;

    if(a<=P[k].l&&P[k].r<=b) return P[k].dat;
    else{
        int v1=query(a,b,(k<<1)+1);
        int v2=query(a,b,(k<<1)+2);
        return max(v1,v2);
    }
}

void debug()
{
    for(int i=0;i<2*n+1;i++){
        printf("i=%d l=%d r=%d dat=%d\n",i,P[i].l,P[i].r,P[i].dat);
    }
}

void debug_dp(int nn)
{
    for(int i=0;i<nn;i++){
        printf("i=%d dp=%d rev_dp=%d..\n",i,dp[i],rev_dp[i]);
    }
}

int main()
{
    int nn,rmax,aim;
    aim=rmax=0;
    scanf("%d",&nn);
    for(int i=0;i<nn;i++){
        scanf("%d",&a[i]);
        rmax=max(rmax,a[i]);
    }
    init(rmax+1);
    for(int i=0;i<nn;i++){
        dp[i]=query(0,a[i]-1,0)+1;
        update(a[i],dp[i]);
        aim=max(dp[i],aim);
    }
    init(rmax+1);
    for(int i=nn-1;i>=0;i--){
        rev_dp[i]=query(a[i]+1,rmax+1,0)+1;
        update(a[i],rev_dp[i]);
    }
    memset(cnt,0,sizeof(cnt));
    for(int i=0;i<nn;i++){
        if(dp[i]+rev_dp[i]-1==aim) cnt[dp[i]]++;
    }
    for(int i=0;i<nn;i++){
        if(dp[i]+rev_dp[i]-1==aim){
            if(cnt[dp[i]]==1) ans[i]=3;
            else    ans[i]=2;
        }
        else ans[i]=1;
    }
    for(int i=0;i<nn;i++) printf("%d",ans[i]);
    puts("");
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值