NOIP 2011TG 解题报告

这两天做了noip2011 的题。
于是来写写题解。
若有错误请大家指出。。。

DAY 1
1、
直接判断点是否在矩形内,看最后一次是哪一张就是了,注意边界。
或者你也可以倒着来 注意 break;
反正对于矩形 四点的坐标确定也就确定了

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
#define mem(a,x) memset(a,0,sizeof(a))

struct node{
  int x,y,a,b;
}q[10050];
int n;
int tax,tay;
int ans;

void readdata(){
  scanf("%d",&n);
  for( int i=1 ;i<=n ;i++)
  {
    scanf("%d%d%d%d",&q[i].x,&q[i].y,&q[i].a,&q[i].b);
  }
  scanf("%d%d",&tax,&tay);
}

int main(){
   readdata();
   for(int i=1; i<=n; i++)
   {
      if(q[i].x<=tax && q[i].x+q[i].a>=tax && q[i].y<=tay && q[i].y+q[i].b>=tay)
      ans=i;
   }
  if(ans == 0)printf("-1");
  else printf("%d",ans);
  return 0;
}

2、
此题首先得理清题意。
好然后谁都一定可以来一个暴力。
然后 你也可以来一个线段树 但有些注意条件

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

using namespace std;
#define GETMID int mid = (l + r) >> 1
#define lson u << 1
#define rson u << 1 | 1
#define Lson u << 1, l, mid
#define Rson u << 1 | 1, mid + 1, r
#define Maxn (200000 + 10)

int n, k, p;
int pri[Maxn];
int col[51][200001];
int kcnt = 0;
int pos[51];
int vis[51] = {0};
long long Ans = 0;

int mi[Maxn * 4];


void Build(int u, int l, int r) {
    if(l == r) {
        mi[u] = pri[l];
        return;
    }
    GETMID;
    Build(Lson);
    Build(Rson);
    mi[u] = min(mi[lson], mi[rson]);
}

int Query(int u, int l, int r, int L, int R) {
    if(L <= l && r <= R) return mi[u];
    GETMID;
    int ret = 1000;
    if(L <= mid) ret = min(ret, Query(Lson, L, R));
    if(R >  mid) ret = min(ret, Query(Rson, L, R));
    return ret;
}

int main() {
    scanf("%d%d%d", &n, &k, &p);
    for(int i = 1; i <= n; ++i) {
        int x;
        scanf("%d%d", &x, &pri[i]);
        if(!vis[x]) {
            vis[x] = 1;
            col[++kcnt][++col[kcnt][0]] = i;
            pos[x] = kcnt;
        }
        else col[pos[x]][++col[pos[x]][0]] = i;
    }
    Build(1, 1, n);

    int last, ns, ret;
    for(int i = 1; i <= kcnt; ++i) {
        last = 1;
        for(int j = 1; j <= col[i][0]; ++j) {
            ret = Query(1, 1, n, col[i][last], col[i][j]);
            if(ret > p) continue;

            Ans += (long long)(j - last) * (long long)(col[i][0] - j + 1);
            last = j;
        }
    }

    printf("%I64d\n", Ans);

    return 0;
}

然后你还可以想一下,得到一些简单的方法。
维护前缀和,count[i][j]为前1..i颜色j的数量,记录最后出现ok[i]=1的i。如果一个区间可以由靠左的ok[i]满足,一定可以由区间内靠右的ok[i]满足,不妨让最靠右的ok[i]满足。从1到n枚举区间右端点。通过前缀和计算左端点的方案数。
我觉得用两个数组记比较好。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

int n,ys,p;
bool ok,f;
int ans=0;
int k[200005],c[200005];
int y[51]={0},s[51]={0},w[51];

int main()
{   
    memset(w,-1,sizeof(w));
    scanf("%d%d%d",&n,&ys,&p);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&k[i],&c[i]);

    for(int i=1;i<=n;i++){
        ok=0;
        if(c[i]<=p)ok=1;
        for(int j=i+1;j<=n;j++){
            if(c[j]<=p)ok=1;
            if(k[j]==k[i]){
                if(ok){
                    ok=0;w[k[i]]=-1;y[k[i]]++;s[k[i]]+=y[k[i]];}
                else{
                    if(w[k[i]]==-1){w[k[i]]=y[k[i]];}               
                    s[k[i]]+=w[k[i]];
                    y[k[i]]++;
                }
                break;}
        }
    }
        for(int i=0;i<ys;i++)
        ans+=s[i];
    printf("%d",ans);
    return 0;
}

3、
这是一道搜索 dfs
不过真的是要打好多啊
判重 剪枝什么的一定要做好

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
struct node
{
    int t[6];
    int c[6][8];
    node()
    {
        memset(t,0,sizeof(t));
        memset(c,0,sizeof(c));
    }
};

int n,ans[6][3];
bool flag,b[6][8],Istrue=false;
node solve(node g)//将标记不用消除的格子依次码好,即完成了清除 
{
    node h;
    for(int i=1;i<=5;i++)
    {
        for(int j=1;j<=g.t[i];j++)
        {
            if(g.c[i][j]&&!b[i][j])
            {
                h.t[i]++;
                h.c[i][h.t[i]]=g.c[i][j];
            }
        }
    }
    return h;
}
node clear(node g)
{
    node h;
    flag=false;
    memset(b,0,sizeof(b));
    for(int i=1;i<=5;i++)//每列 
    {
        for(int j=1;j<=g.t[i];j++)//每列从下到上的棋子 
        {
            if(i<=3)//横向
            {
                if(g.c[i][j]==g.c[i+1][j]&&g.c[i][j]==g.c[i+2][j])//连续三个相同 
                {
                    b[i][j]=b[i+1][j]=b[i+2][j]=true;//标记要消掉的格子 
                    flag=true;
                }
            }
            if(j<=g.t[i]-2)//纵向 
            {
                if(g.c[i][j]==g.c[i][j+1]&&g.c[i][j]==g.c[i][j+2])//同上 
                {
                    b[i][j]=b[i][j+1]=b[i][j+2]=true;
                    flag=true;
                }
            }
        }
    }
    if(flag)//如果有三个颜色连续的棋子 
    {
        h=solve(g);//清除 
        return clear(h);//直到不能清除为止 
    } else return g;
}
void print()
{
    for(int i=1;i<=n;i++)
        printf("%d %d %d\n",ans[i][0],ans[i][1],ans[i][2]);
    Istrue=true;
}

void dfs(node g,int k)
{
    int l;
    node h;
    flag=true;
    for(int i=1;i<=5;i++)
    {
        if(g.t[i])//如果还没有完全消除 
        {
            flag=false;
            break;
        }
    }
    if(flag)//如果刚好在第n步完全消除则输出 
    {
        if(k>n) print(); else return;
    }
    if (Istrue) return ;//如果over
    if(k>n) return;//如果超过n步 
    for(int i=1;i<=5;i++)//对于每列 
    {
        for(int j=1;j<=g.t[i];j++)//每行 
        {
            if(i<5)//前4列,考虑右移的情况,最优化剪枝,右移优于左移 
            {
                if(j>g.t[i+1])//如果该列高于第后一列 ,即可以直接右移 
                {
                    h=g;//直接移动 
                    h.t[i+1]++;
                    h.c[i+1][h.t[i+1]]=h.c[i][j];
                    h.c[i][j]=0;
                    h.t[i]--;
                    for(l=j;l<=h.t[i];l++)//第i列悬空的下落 
                        h.c[i][l]=h.c[i][l+1];
                    h=clear(h);//清除 
                    ans[k][0]=i-1;//记录所走方法 
                    ans[k][1]=j-1;
                    ans[k][2]=1;
                    dfs(h,k+1);//继续走下一步 
                    if (Istrue) return ;
                }
                else//需要交换 
                {
                    h=g;
                    l=h.c[i][j];//交换两个棋子 
                    h.c[i][j]=h.c[i+1][j];
                    h.c[i+1][j]=l;
                    h=clear(h);//清除 
                    ans[k][0]=i-1;//记录方案 
                    ans[k][1]=j-1;
                    ans[k][2]=1;
                    dfs(h,k+1);//继续走下一步 
                    if (Istrue) return ;
                }
            }

            if(i>1)//如果是后4列,需要考虑左移的情况 
            {
                if(j>g.t[i-1])//如果该列高于前一列 
                {
                    h=g;//直接移动 
                    h.t[i-1]++;
                    h.c[i-1][h.t[i-1]]=h.c[i][j];
                    h.c[i][j]=0;
                    h.t[i]--;
                    for(l=j;l<=h.t[i];l++)
                        h.c[i][l]=h.c[i][l+1];

                    h=clear(h);//清除 
                    ans[k][0]=i-1;//记录方案 
                    ans[k][1]=j-1;
                    ans[k][2]=-1;
                    dfs(h,k+1);
                    if (Istrue) return ;
                }
            }
        }
    }
}

int main()
{
    int j;
    node g;
    scanf("%d",&n);
    for(int i=1;i<=5;i++)//读入棋盘初始状态 
    {
        scanf("%d",&j);
        while(j)
        {
            g.t[i]++;
            g.c[i][g.t[i]]=j;
            scanf("%d",&j);
        }
    }
    dfs(g,1);//从第一步开始搜索 
    if (!Istrue) printf("-1\n");
    return 0;
}

DAY 2
1、
数学题对吧
恩那就不多说了 注意时刻取模,还有组合数的递推公式。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 10007;

int a,b,k,n,m;
int f[1005][1005];
int ans;
int main(){
  init();
  scanf("%d%d%d%d%d",&a,&b,&k,&n,&m);
  a%=M;  b%=M;
  for(int i=1;i<=k;i++) {f[i][i]=f[i][0]=1;f[i][1]=i;}

  for(int i=3;i<=k;i++)
        for(int j=2;j<i;j++)
            f[i][j]=(f[i-1][j]+f[i-1][j-1])%M;

  ans = f[k][m] %M;
  for(int i=1 ;i<=n ;i++) ans=(ans*a) % M;
  for(int i=1 ;i<=m ;i++) ans=(ans*b) % M;
  printf("%d",ans); 
}

2、
这道题读完题后应该会想到二分吧。
注意初始的ans要long long 还要附一个很大很大的值。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
#define  LL long long 

LL ans=1e14; 
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

LL lmax(LL x,LL y){
 if(x>y) return x;
 return y;
}
LL lmin(LL x,LL y){
 if(x<y) return x;
 return y;
}
LL labs(LL x){
 if(x>0) return x;
 return -x;
}
int mw=-1;
int n,m;
LL S;
int w[200005],v[200005];
int l[200005],r[200005];

LL sum[200005],c[200005];

LL cal(int x){
   LL tem = 0;
   for(int i=1 ;i<=n ;i++){
    sum[i] = sum[i-1];
    c[i] = c[i-1];
     if(w[i]>=x)
        {
            sum[i]+=v[i];
            c[i]++;
        }
    }
    for(int i=1;i<=m;i++)
    {
        tem+=(c[r[i]]-c[l[i]-1])*(sum[r[i]]-sum[l[i]-1]);
    }
    return tem;    
}


int main(){
  n = read(); m = read(); scanf("%I64d",&S);  
  LL mw;
  for(int i=1 ;i<=n ;i++)
  {w[i] =read();
   v[i] =read();
   mw=lmax(mw,w[i]);  
  }
  for(int i=1 ;i<=m ;i++)
  {l[i] =read();
   r[i] =read(); 
  }

  int l=0;
  int r=mw+1; 

  while(l < r){
    int mid=(l+r)>>1;
    LL t=cal(mid);
    LL cmp =labs(t-S);
    ans=lmin(ans,cmp);
    if(t<=S)r=mid;
    else l=mid+1;
  } 
  printf("%I64d",ans);
 return 0;
}

3、
最后一道题
要是没想到的话过掉一点还是可行的。
然后这道题肯定是贪心
发现给某个D[i]减一后会使后面连续的人等车的车站的乘客提前上车。直到一个车站leave[j]>get[j](车等人),人来的时间是不能跟改变的。所以提前了上车时间的旅客提前了的一分钟要在等人上浪费掉,所以旅行时间不会减小。也就是说令right[i]为i后面第一个满足车等人的站点 那么在i+1到right[i]这个区间内下车的人的旅行时间都会减小一。维护下车人数的前缀和S。
对于K
1.k很小全部用掉
2.D[i]很小全部减掉(必须保证每个D[i]>=0)
3.i+1..tright[i]中最小的Get[j]-Leave[ j ] (把这个值减成负的会破坏现有的区间的关系,减成负的就变成车等人了)

#include<stdio.h>
#include<stdlib.h>
#define max(a,b) (a>b ? (a):(b))

long long i,j,k,n,m,l;
long long time[1100],f[1100],g[1100],sum[1100],d[1100];
long long a[11000],b[11000],t[11000],ans;

void init()
{
     long long i,j;
     scanf("%I64d%I64d%I64d",&n,&m,&k);
     for (i=1;i<n;i++)
         scanf("%I64d",&d[i]);
     for (i=1;i<=m;i++)
     {
         scanf("%I64d%I64d%I64d",&t[i],&a[i],&b[i]);
         sum[b[i]]++;
         f[a[i]]=max(f[a[i]],t[i]);
     }

     return ;
}
void in_time()
{
     long long i,j,l;

     time[1]=0;
     for (i=1;i<=n;i++)
         time[i]=max(time[i-1],f[i-1])+d[i-1];
     g[n]=n;
     g[n-1]=n;
     for (i=n-2;i>=1;i--)
         if (time[i+1]<=f[i+1])
            g[i]=i+1;
         else g[i]=g[i+1];
     for (i=1;i<=m;i++)
         ans+=time[b[i]]-t[i];
     for (i=1;i<=n;i++)
         sum[i]=sum[i-1]+sum[i];
     return ;
}
void work()
{
     long long i,j;
     long long maxs=0,x=0;

     for (i=1;i<n;i++)
         if (maxs<sum[g[i]]-sum[i]&&d[i]>0)
            maxs=sum[g[i]]-sum[i],
            x=i;
     long long l=x,r=g[x];
     d[x]--;
     ans-=maxs;
     if (r>n-1)
        r=n-1;
     for (i=l;i<=r;i++)
         time[i]=max(f[i-1],time[i-1])+d[i-1];
     for (i=r;i>=l;i--)
         if (time[i+1]<=f[i+1])
            g[i]=i+1;
         else
         g[i]=g[i+1];

     return ;
}
int main()
{   
    init();
    in_time();
    for (i=1;i<=k;i++)
    work();

    printf("%I64d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值