高端的N 校联考题

序列

【问题描述】
作为一名火星人,你为了占领地球,需要想方设法使地球人失去信心。现在你获得了一项能力,控制今后n天的天气温度,对于第i天,你能将温度控制在[ai,bi]中任意一个数字,你的目的是使其中某段时间,温度持续不下降,趁此来攻击地球。现在问你最多可以使连续的多少天满足温度不下降。
【输入】 第一行给出一个整数n,表示你能控制的天数。 接下来n行,第i行给出2个整数ai,bi,表示你能控制的天气范围。保证ai<=bi。
【输出】 输出一个整数,表示答案。
【输入输出样例】
sequence.in
4
1 3
2 4
1 1
3 4
sequence.out
2
【数据范围】
对于20%的数据 3<=n<=10;
对于40%的数据 3<=n<=3000;
对于60%的数据 3<=n<=100000;
对于100%的数据 3<=n<=1000000,1<=ai,bi<=100000。

先以为是和如lis一样的简单dp
然后就那样写了 呵呵 出题人数据不卡这个 80……
因为如果从第一个开始可能并不一定是最优解
所以用这个思想 还要加上单调队列来维护

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

void init()
{
  freopen("sequence.in","r",stdin);
  freopen("sequence.out","w",stdout);
}

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

int n;
int a[1000005],b[1000005];
int ans=1;
int q[1000005];
int q_top=1,q_bot=0;
int main(){
  init();
  n = read();
  for(int i=1 ;i<=n ;i++)
  {a[i]=read();b[i]=read();}  

  for(int i=1 ;i<=n ;i++)
  {  
     while(q_top <= q_bot && a[q[q_top]] > b[i])
     ++q_top;
     ans = max(ans , i - q[q_top-1]);
     while(q_top <= q_bot && a[i]>= a[q[q_bot]])
     --q_bot;
     q[++q_bot] = i;    
  }  

  printf("%d",ans);
  return 0;
}

循环整数

【问题描述】
moreD在学习完循环小数之后发现循环是个很美好的性质。自己只需要记住短短的循环节以及循环次数(次数大于1,且是整数)就可以记住整个数字了。
因为背诵数字变得方便了,moreD决定背诵[L,R]内的所有循环的整数。moreD的背诵计划有T天,但是他不知道每天具体要背多少个数,请你帮助moreD计算出每天需要背诵的数字个数。
如果moreD在某天遇到一个曾经背过的数字,他会义无反顾地重新背诵。
【输入格式】 第一行给出一个整数T,表示moreD计划背诵T天的数字。 接下来n行,第i行给出2个整数Li,Ri,表示moreD第i天的背诵计划。
【输出格式】 输出T行,每行一个整数,表示第i天moreD需要背诵的数字个数。
【输入输出样例】
circulate.in
3
1 10000
55555 66666
10 100
circulate.out
108
2
9
【数据范围】
对于30%的数据 T*MAX{Ri}<=2*10^6
对于70%的数据MAX{Ri}<=2*10^6
对于100%的数据 T<=50000,1<=Li<=Ri<=2*10^18
【样例解释】
对于第2天,moreD只需要背诵55555,66666.
对于第3天,moreD只需要背诵11,22,33,44,55,66,77,88,99.

我只知道我做了2个小时也没做出来
当然可以暴力其实那点小数据还是可以的
以下是题解

对于[L,R]内的循环整数个数,可以看成是[1,R]内的循环整数个数减去[1,L-1]内的循环整数个数。
{对于确定了循环节长度i以及数字长度n的循环整数,在[1,x]内的个数可以用MAX{x div k-10^(i-1)+1,0}算出,其中k是i-1个0,1个1,循环n div i次所得的数字。
例如,若求[1,666666]内长度为6的循环节长度为3的数字只需要用666666 div 1001-100+1=567.如此即可快速算出数字个数。这个公式成立当且仅当x的数字长度等于n。也就是说[1,666666]内长度为5的循环节长度为1的数字等于99999 div 11111-1+1=9.不能使用666666作为被除数.}(暴力枚举长度判断也可.}
所以就可以枚举循环节长度以及数字长度,利用以上公式算出循环整数的数字个数。
但是,很容易发现,这种做法会引起重复计算。因为如果一个整数的循环节长度是i,那只要k*i是数字长度的约数,k*i也是该整数的循环节长度,所以每次计算循环节长度为i的循环整数个数的时候也要把循环节长度为i的约数的所有合法循环整数减去。
也就是说,9999在循环节长度是1的时候已经算了一遍,而在循环节长度为2的时候也会算一次,所以要减去。
时间复杂度为O(T*(lgMAX{R})^2),期望得分100%。

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

void init()
{
  freopen("circulate.in","r",stdin);
  freopen("circulate.out","w",stdout);
}
int ans=0;

inline long long read(){
  long long 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;
}
/*-----------------------------------------------------*/

const int MaxNL = 20;
long long tab[MaxNL];
long long orz[MaxNL];
//hard to think 
inline long long count(long long x)
{
    int l = 0, d[MaxNL];
    while (x > 0)
        d[++l] = x % 10, x /= 10;//!各!位数 

    long long  ans = 0;
    for (int i = 1; i < l; ++i)
        ans += tab[i]; //满方案数 

    long long s = 1;
    for (int j = 1; j < l; ++j, s *= 10)
        if (l % j == 0)
        {
            long long t = 0;
            for (int i = l; i > l - j; --i)
                t = t * 10 + d[i];

            long long p = 0;
            bool cutHand = false;
            for (int i = l - j; i > 0; --i)
            {
                p = p * 10 + d[i];
                if (i % j == 1 || j == 1)
                {
                    if (p < t)
                        cutHand = true;
                    else if (p > t)
                        break;
                    p = 0;
                }
            }

            orz[j] = t - s - cutHand + 1;
            for (int k = 1; k < j; ++k)
                if (j % k == 0)
                    orz[j] -= orz[k];
            ans += orz[j];
        }
    return ans;
}

void prepare(){
for (int i = 1; i < MaxNL; ++i)  //位数 
    {
        long long s = 9;          // 首位 
        for (int j = 1; j < i; ++j)//枚举约数 可循环位数 
            {
            if (i % j == 0)
            {
                orz[j] = s;
                for (int k = 1; k < j; ++k)
                    if (j % k == 0)
                        orz[j] -= orz[k]; //减去约数的约数 
                tab[i] += orz[j];//加上当前方案数 
            }
            s *= 10;
            }
    }
}

int T;
long long l,r;
int main(){
    init();
    prepare();
    /*
    for(int i = 1 ;i<=18 ;i++)printf("%d ",tab[i]);
    printf("\n");
    for(int i = 1 ;i<=18 ;i++)printf("%d ",orz[i]);
    /**/
    T=read();
    while(T--){
      l=read();r=read();
      long long ans = count(r) - count(l-1);
      printf("%lld\n",ans);
    } 
return 0;
}

小Y的炮

【问题描述】
小Y最近开发出了批量制造大威力轰山炮的方法。才过去不到几个月,小Y就制造出了M门款式不同的轰山炮。第i门轰山炮发射一次,能使一座当前高度不高于Ai的山的高度降低Di(当然山的高度不能轰到0以下)。应政府要求,小Y要用他开发的轰山炮轰平开发区的几座山。由于开发区急需土地资源,政府要求小Y轰平尽量多的山(轰平:使山的高度降低至0)。
但是小Y制造的弹药有限,导致他最多只能发射K次。
小Y想知道,他最多能轰平几座山?轰平这些山后,弹药最多还够他发射几次?
【输入】
第一行三个正整数N,M,K,分别表示山的数目、轰山炮的款式数目、最多发射次数。
接下来N行,每行一个正整数Hi,表示第i座山的高度,输入数据保证Hi是降序的(从大到小)。
接下来M行,每行两个正整数Ai,Di,分别表示第i款轰山炮能轰的山的最高高度,和轰掉的山高度的减少值。
【输出】
一行两个整数Max,Remain,分别表示最多轰平的山的数目和轰平这些山后最多的剩余发射次数。
【输入输出样例】
cannon.in
3 2 3
8
6
2
10 6
6 5
cannon.out
2 1
【数据范围】
20%的数据满足N<=100,M<=100,Hi,Ai<=100。
50%的数据满足N<=1000,M<=500。
80%的数据满足N<=250000,M<=500。
20%、50%、80%的数据均满足Hi,Ai<=1000000。
100%的数据满足N<=250000,M<=500,K,Hi,Ai<=10^18,Di<=500。

贪心当然是一种优秀的策略
注意判断一些不必要的条件50还是可以的

当然正解不是这样的
以下
100%:注意到D的范围很小。假如一个高度H在区间i内,一直降低,在H刚好降低到刚好比A[i-1]小时,总有A[i-1]-H<=D。对于任何高度都有上述结论。所以我们最多只需处理O(M*D)个高度即可。令F[H]为轰平H高度的山的最少发射次数,则对于某个高度H,一直降至H-t*Di<=A[i-1],有F[H]=F[H-t*Di]+t,由于有用的H最多只有O(M*D)个,所以这样做的效率为O(M*D)。(F数组可由HASH代替)
那么对于从低到高的每座山,设该山高度为H,找到H对应的区间i(O(N+M)),将H降至H-t*Di<=A[i-1],那么最少发射次数为t+F[H-t*Di],其中H-t*Di必然是O(M*D)个高度中的一个。所以对于每座可以被轰的山,都可以用O(1)的时间得到轰平所需的最少发射次数。

不完全正确的代码
(不是我写的)………
因为我还没全对

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;

void init()
{
  freopen("cannon.in","r",stdin);
  freopen("cannon.out","w",stdout);
}
const int maxn=250004;
struct data{
    ll a,d;
    bool operator <(const data &b)const{
        return (a<b.a || (a==b.a && d<b.d));
    }
}cnn[maxn],p[maxn];
ll k,h[maxn],ans=0;
int n,m,cnt=0;
map<ll,ll>f;

inline ll get(){
    char c;while(!isdigit(c=getchar()));
    ll v=c-48;while(isdigit(c=getchar()))v=v*10+c-48;
    return v;
}
int main(){
    init();
    n=get();m=get();k=get();
    for(int i=n;i>=1;--i)h[i]=get();
    for(int i=1;i<=m;++i)cnn[i].a=get(),cnn[i].d=get();
    sort(cnn+1,cnn+1+m);
    for(int i=1;i<=m;++i){
        while(cnt && p[cnt].d<=cnn[i].d)--cnt;
        p[++cnt]=cnn[i];
    }
    ll dn=0;f[0]=0;
    for(int i=1;i<=cnt;++i){
        if(i!=1)dn=p[i-1].a;
        ll l=max(dn,p[i].a-p[i].d);
        for(ll j=l+1;j<=p[i].a;++j){
            ll t=(j-dn-1)/p[i].d+1;
            f[j]=f[max(0ll,j-(p[i].d*t))]+t;
        }
    }
    int j=1;
    dn=0;
    for(int i=1;i<=n;++i){
        while(j<=cnt && p[j].a<h[i])++j;
        if(j>cnt)break;
        if(j!=1)dn=p[j-1].a;
        ll t=(h[i]-dn-1)/p[j].d+1;
        ll q=f[max(0ll,h[i]-(p[j].d*t))]+t;
        if(k>=q)k-=q,++ans;else break;
    }
    printf("%lld ",ans);
    printf("%lld\n",k);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值