清北

                        笔记
【问题描述】
给定一个长度为m的序列a,下标编号为1~m。序列的每个元素都是1~n的
整数。定义序列的代价为

你现在可以选择两个数x和y,并将序列a中所有的x改成y。x可以与y相等。
请求出序列最小可能的代价。
【输入格式】
输入第一行包含两个整数n和m。第二行包含m个空格分隔的整数,代表序
列a。
【输出格式】
输出一行,包含一个整数,代表序列最小的代价。
【样例输入 1】
4 6
1 2 3 4 3 2
【样例输出 1】
3
【样例输入 2】
10 5
9 4 3 8 8
【样例输出 1】
6
【样例解释】
样例 1 中,最优策略为将 4 改成 3。样例 2 中,最优策略为将 9 改成 4。
【数据规模和约定】

对于30%的数据,n,m<=100.
对于60%的数据,n,m ≤ 2000。
对于100%的数据,1 ≤ n,m≤ 100,000。

 

 

题解:由于题目要求序列的最小代价,因此原代价-max(x与它在序列中左右两边数的差值-替换他的y在序列中左右两边数的差值)即为答案。

        考虑用哪个数替换x:

   当与x相邻的数有奇数个时,取中间的数为y。因为改变中间的数对两边的贡献一样。比如样例1:与2相邻的为1,3,3,原本与2相邻的代价为1,1,1。将2替换为3时,代价为2,0,0.左边的代价加1,右边的代价-1.相当于改变中间的数对其两边代价无影响,影响的只是中间数的代价,因此取中间数为y,此时中间数代价为0。而若是取其他的数,对其左边或右边代价产生影响。

   当与y相邻的数有偶数个时,取中间两个数之间的任意数为y均可。比如样例1:与3相邻的为2,2,4,4。取2<=y<=4均可。原本与3相邻的代价为1,1,1,1。总代价为4.将3替换为2时,代价为0,0,2,2.总代价为4.将3替换为4时,代价为2,2,0,0,总代价也为4.

 

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
#define N 100010
using namespace std;
ll n,m,ans=0x7fffffff/3,sum(0);
ll a[N],b[N],c[N];
ll f[N][3],ff[N];
bool fi[N]={0};
void erfen(int k,int l,int r)
{
    if (l>r) return ;
    int mid=(l+r)>>1;
    a[0]=mid;
    int ki=b[k],sum1(0),sum2(0);
    if (mid-a[b[k]-1]>0) sum1+=mid-a[b[k]-1];
      else sum2+=mid-a[b[k]-1];
    if (a[b[k]+1]-mid>0) sum2+=mid-a[b[k]+1];
      else sum1+=mid-a[b[k]+1];
    while (ff[ki])
      {
           if (mid-a[ff[ki]-1]>0) sum1+=mid-a[ff[ki]-1];
             else sum2+=mid-a[ff[ki]-1];
           if (a[ff[ki]+1]-mid>0) sum2+=mid-a[ff[ki]+1];
           else sum1+=mid-a[ff[ki]+1];
           ki=ff[ki];
      }
    ans=min(ans,sum-f[k][0]+f[k][1]-f[k][2]+sum1-sum2);
    if (-sum2>sum1) erfen(k,mid+1,r);
    else erfen(k,l,mid-1);
    
}
int main()
{
    freopen("note.in","r",stdin);
    freopen("note.out","w",stdout);
    
    scanf("%I64d%I64d",&n,&m);
    for (int i=1;i<=m;i++) 
      {
           scanf("%I64d",&a[i]);
           fi[a[i]]=1;
           a[0]=a[1];
           if (a[i]-a[i-1]>0)
             {
                    f[a[i]][0]=a[i]-a[i-1],sum=sum+a[i]-a[i-1];    
               f[a[i-1]][2]=a[i]-a[i-1]; 
           } 
         else 
           {
                 f[a[i]][1]=a[i]-a[i-1],sum=sum+a[i-1]-a[i];
                 f[a[i-1]][2]=a[i-1]-a[i];
           }
          
      }
    for (int i=m;i>=1;i--)
      {
           if (!b[a[i]]) b[a[i]]=i;
           else  ff[i]=b[a[i]],b[a[i]]=i;
      }
    for (int i=1;i<=n;i++)
      if (fi[i]) erfen(i,0,n);
    printf("%I64d\n",ans);
      
    fclose(stdin);
    fclose(stdout);
    
    return 0;
    
}
我可怜的25分代码

考试的时候从0~n枚举将x变为几。

 

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<ios>
#include<vector>
#define ll long long
using namespace std;
const int N = (int) 1e5;
vector<int> v[N+10];
int a[N+10];
int n,m;
ll ans(0),sum(0);
int main()
{
    freopen("note.in","r",stdin);
    freopen("note.out","w",stdout);
    
       scanf("%d%d",&n,&m);
       for (int i=1;i<=m;i++) scanf("%d",&a[i]);    
       for (int i=1;i<=m;i++)
         {
            if (i!=1&&a[i]!=a[i-1]) v[a[i]].push_back(a[i-1]);//将与a[i]相邻的不同的数储存进数组 
         if (i!=m&&a[i]!=a[i+1]) v[a[i]].push_back(a[i+1]);     
      }
    for (int i=1;i<=n;i++)
      {
           int k=v[i].size();
           if (k)
             {
                   sort(v[i].begin(),v[i].end());
                  ll ki=v[i][k>>1];      //当k为奇数时,取中间的数,其对两边数的贡献是一样的。 
                                         //当k为偶数时,取中间两个数之间的任意数都可,对其两边数的贡献一样。 
                   ll sum1(0),sum2(0);
                   for (int j=0;j<k;j++)
                     {
                            sum1+=(ll)abs(i-v[i][j]);
                            sum2+=(ll)abs(ki-v[i][j]);
                }
             ans=max(ans,sum1-sum2);
             sum+=sum1;
           } 
      }
    cout<<sum/2-ans<<endl;//由于a[i+1]-a[i]在计算a[i]时计算了一次。
                         //在a[i+1]时又计算了一次,因此除以2 
    fclose(stdin);
    fclose(stdout);
    return 0;
    
} 
100分

 

 

 

                     括号
【问题描述】
有一个长度为n的括号序列,以及k种不同的括号。序列的每个位置上是哪
种括号是随机的,并且已知每个位置上出现每种左右括号的概率。求整个序列是
一个合法的括号序列的概率。
我们如下定义合法括号序列:
 空序列是合法括号序列;
 如果A是合法括号序列,那么lAr是合法括号序列,当且仅当l和r是同种
的左右括号;
 如果A和B是合法括号序列,那么AB是合法括号序列。
【输入格式】
输入第一行包含两个整数n和k。接下来的输入分为n组,每组k行。第i组第
j行包含两个实数l[i,j]和r[i,j],分别代表第i个位置上是第j类的左括号和右括号
的概率。
【输出格式】
输出一行,包含一个实数,代表序列是合法括号序列的概率。建议保留至少
5 位小数输出。只有当你的输出与标准答案之间的绝对误差不超过10 −5 时,才会
被判为正确。
【样例输入 1】
2 1
1.00000 0.00000
0.00000 1.00000
【样例输出 1】
1.00000
【样例输入 2】
4 1
0.50000 0.50000
1.00000 0.00000
0.00000 1.00000
0.50000 0.50000
【样例输出 2】
0.25000
【数据规模和约定】
对于20%的数据,n ≤ 50,k = 1,所有位置的概率非 0 即 1。
另外有 30%的数据,n ≤ 34,k = 1,前 10 个和后 10 个位置的所有概率都
是 0.5,中间剩余位置的概率非 0 即 1。
80%的数据,n,k ≤ 50。
对于100%的数据,1 ≤ n ≤ 200,1 ≤ k ≤ 50。

考场:考试的时候只想到了50%的数据怎么做,画蛇添足加上了关于输出结果是0的判断,只得了30分,然而删掉后,可以得50分。好伤心。

 

题解:空序列直接判断n是否等于0.用f数组储存第二种括号序列概率,用g数组储存第三种括号序列概率。

        枚举序列的长度l,f[i][i+l-1]+=(f[i+1][i+l-2]+g[i+1][i+l-2])*L[i][j]*R[i+l-1][j];//在合法序列两边加上一对括号,概率就是中间是合法括号序列的概率*序列开头是左括号的概率*序列结尾是右括号的概率。

        g[i][i+l-1]+=f[i][j]*(f[j+1][i+l-1]+g[j+1][i+l-1]);//前一部分括号序列为f的概率和后一部分是合法括号序列的概率。//为什么前一部分只有f的概率?例如:()()()这个好像可以从位置5Chu前一部分分成g序列,后一部分f序列,但实际当你在求位置2的时候,这种情况已经计算了。

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
#define N 210
using namespace std;
int n,k;
double a[N][N][3],f[N][N][N]={0},ans(0);
int num[N][2]={0};
int main()
{
    freopen("brackets.in","r",stdin);
    freopen("brackets.out","w",stdout);
    
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++)
      for (int j=1;j<=k;j++)
          {
            double x,y;
            scanf("%lf%lf",&x,&y);
            a[i][j][1]=x;
            a[i][j][2]=y;
          }
    for (int i=1;i<=k;i++) f[1][i][1]=a[1][i][1];
    for (int i=2;i<=n;i++)
      {
          for (int ki=1;ki<=k;ki++)
            for (int j=0;j<i;j++)
              {
                 f[i][ki][j+1]+=f[i-1][ki][j]*a[i][ki][1];
                 if (j-1>=0) f[i][ki][j-1]+=f[i-1][ki][j]*a[i][ki][2];    
            }
      }
    for (int i=1;i<=k;i++) ans+=f[n][i][0];
    printf("%.5lf\n",ans);
    fclose(stdin);
    fclose(stdout);
    
    return 0;
    
}
50分

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define N 210
#define M 60
using namespace std;
int n,k;
double L[N][M],R[N][M],f[N][N]={0},g[N][N]={0};
int main()
{
    freopen("brackets.in","r",stdin);
    freopen("brackets.out","w",stdout);
    scanf("%d%d",&n,&k);
    if (n==0) printf("%0.5lf\n",1);//空序列 
    else 
      {
          for (int i=1;i<=n;i++)
          for (int j=1;j<=k;j++)
            scanf("%lf%lf",&L[i][j],&R[i][j]);
        for (int i=1;i<=n;i++) f[i+1][i]=1;
          for (int l=2;l<=n;l+=2)
            for (int i=1;i<=n-l+1;i++)
              {
                 for (int j=1;j<=k;j++) f[i][i+l-1]+=(f[i+1][i+l-2]+g[i+1][i+l-2])*L[i][j]*R[i+l-1][j];
                 for (int j=i+1;j<i+l-1;j+=2) g[i][i+l-1]+=f[i][j]*(f[j+1][i+l-1]+g[j+1][i+l-1]);
              }
         double ans=f[1][n]+g[1][n];
         printf("%.5lf\n",ans);
      }
    fclose(stdin);
    fclose(stdout);
    return 0;
} 
dp(100分)

 

 

                    城堡
【问题描述】
给定一张N个点M条边的无向连通图,每条边有边权。我们需要从M条边中
选出N − 1条, 构成一棵树。 记原图中从 1 号点到每个节点的最短路径长度为Di ,
树中从 1 号点到每个节点的最短路径长度为? ? ,构出的树应当满足对于任意节点
i,都有Di = Si 。
请你求出选出N − 1条边的方案数。
【输入格式】
输入的第一行包含两个整数N和M。
接下来M行,每行包含三个整数u、v和w,描述一条连接节点u和v且边权为
w的边。
【输出格式】
输出一行,包含一个整数,代表方案数对2^31 − 1取模得到的结果。
【样例输入】
3 3
1 2 2
1 3 1
2 3 1
【样例输出】
2
【数据规模和约定】
对于20%的数据,2<=N<=5,M<=10.
对于50%的数据,满足条件的方案数不超过 10000。
对于100%的数据,2≤ N ≤ 1000,N-1  ≤ M ≤ N(N-1)/2,1<=w<=100.

 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define N 1100
#define NN 1000010
#define ll long long
using namespace std;
int n,m,num(0),Head,Tail;
int head[N],team[NN];
ll sum[N]={0},ans(1);
ll dis[N];
bool f[N];
const ll M=1<<31-1;
struct node
{
    int v,t,pre;
}e[NN];
void xx()
{
    Head=0;Tail=1;
    memset(f,0,sizeof(f));
    memset(team,0,sizeof(team));
    team[1]=1;f[1]=1;sum[1]=1;
}
void add(int from,int to,int w)
{
    e[++num].v=to;
    e[num].t=w;
    e[num].pre=head[from];
    head[from]=num;
}
void spfa()
{
    for (int i=1;i<=n;i++) dis[i]=0x7fffffff;
    dis[1]=0;f[1]=1;team[1]=1;Head=0;Tail=1;
    while (Head<=Tail)
      {
           int ki=team[++Head];
           f[ki]=0;
           for (int i=head[ki];i;i=e[i].pre)
             {
                    int u=e[i].v;
                 if (dis[u]>dis[ki]+e[i].t)
                      {
                            dis[u]=dis[ki]+e[i].t;
                            if (!f[u])
                              {
                                  f[u]=1;
                                  team[++Tail]=u;
                        }
                  }
           }
      }
}
int main()
{   
    freopen("castle.in","r",stdin);
    freopen("castle.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1,x,y,z;i<=m;i++)
      {
           scanf("%d%d%d",&x,&y,&z);
           add(x,y,z);
           add(y,x,z);
      }
    spfa();
    xx();
    while (Head<=Tail)
      {
           int ki=team[++Head];
           f[ki]=0;
           for (int i=head[ki];i;i=e[i].pre)
             {
                    int u=e[i].v;
                 if (dis[u]==dis[ki]+e[i].t) 
                  {
                       sum[u]++;
                       if (sum[u]>=M) sum[u]-=M;
                       if (!f[u]) team[++Tail]=u,f[u]=1;
                  }
           }
      }
    for (int i=1;i<=n;i++) ans=(ans*sum[i])%M;
    cout<<ans<<endl;    
    fclose(stdin);
    fclose(stdout);
    return 0;
} 
需更正

 

posted @ 2016-11-13 17:25  外婆桥  阅读(218)  评论(0编辑  收藏  举报