zoukankan      html  css  js  c++  java
  • test20190519 NOIP 模拟赛 3

    序列

    【问题描述】
    作为一名火星人,你为了占领地球,需要想方设法使地球人失去信心。现在你获得了一项能力,控制今后 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。

    考场50分

    考虑到转移情况,应该是一段有交集的区间,然后若区间右端点后一个温度大于交集,则可转移。

    用st表维护温度交集,每次二分查找。时间复杂度(O(nlog n))

    #include<bits/stdc++.h>
    #define co const
    template<class T>T read(){
    	T data=0,w=1;char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    	for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    	return data*w;
    }
    template<class T>T read(T&x){
    	return x=read<T>();
    }
    typedef long long ll;
    using namespace std;
    typedef pair<int,int> pii;
    
    co int N=1e6+2;
    int n,lg[N];
    pii st[N][21];
    pii merge(co pii&a,co pii&b){
    	return pii(max(a.first,b.first),min(a.second,b.second));
    }
    pii query(int l,int r){
    	int k=lg[r-l+1];
    	return merge(st[l][k],st[r-(1<<k)+1][k]);
    }
    bool check(co pii&a){
    	return a.first<=a.second;
    }
    int f[N],ans;
    int main(){
    	freopen("sequence.in","r",stdin),freopen("sequence.out","w",stdout);
    	read(n),lg[0]=-1;
    	for(int i=1;i<=n;++i) read(st[i][0].first),read(st[i][0].second),lg[i]=lg[i>>1]+1;
    	for(int j=1;j<=lg[n];++j)
    		for(int i=1,t=n-(1<<j)+1;i<=t;++i)
    			st[i][j]=merge(st[i][j-1],st[i+(1<<j-1)][j-1]);
    	for(int i=n;i;--i){
    		int l=i,r=n;
    		while(l<r){
    			int mid=l+r+1>>1;
    			if(check(query(i,mid))) l=mid;
    			else r=mid-1;
    		}
    		f[i]=l-i+1;
    		if(st[l+1][0].first>=query(i,l).second) f[i]+=f[l+1];
    		ans=max(ans,f[i]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    std

    若第 i 天的温度 K,那么第 i+1 天的温度(假设 b[i+1]>=k)必定为 max(k,a[i+1])(贪心)。
    所以若答案区间为[l,r],那么第 r 天的温度必定为
    max(a[k])(l<=k<=r)。若我们已知第 1 天最远能达到第 r 天,那么第 2 天必定至少能达到第 r
    天,并且温度为 max(a[k],2<=k<=r),然后再贪心的向后扩展即可。为了快速知道 max(a[k]),
    需要维护一个单调队列。
    另外动规+单调队列的方法也可以。
    效率 o(n);

    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    inline int getint()
    {
    	static char c;
    	while ((c = getchar()) < '0' || c > '9');
    
    	int res = c - '0';
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = res * 10 + c - '0';
    	return res;
    }
    
    inline void relax(int &a, const int &b)
    {
    	if (b > a)
    		a = b;
    }
    
    const int MaxN = 1000005;
    
    int n;
    int lower[MaxN], upper[MaxN];
    
    int q_top = 1, q_bot = 0;
    int q[MaxN];
    
    int main()
    {
    	freopen("sequence.in", "r", stdin);
    	freopen("sequence.out", "w", stdout);
    
    	n = getint();
    	for (int i = 1; i <= n; ++i)
    	{
    		lower[i] = getint();
    		upper[i] = getint();
    	}
    
    	int res = 1;
    	for (int i = 1; i <= n; ++i)
    	{
    		while (q_top <= q_bot && lower[q[q_top]] > upper[i])
    			++q_top;
    		relax(res, i - q[q_top - 1]);
    		while (q_top <= q_bot && lower[i] >= lower[q[q_bot]])
    			--q_bot;
    		q[++q_bot] = i;
    	}
    
    	cout << res;
    
    	fclose(stdin);
    	fclose(stdout);
    	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.

    考场做法

    我的思路是,用solve(R)-solve(L-1)
    先计算出solve(limit)中limit的位数n,然后枚举循环节长度i和循环次数j,根据乘积i*j分为两种情况:

    1. i*j<n,那么此时的答案=9*10^{i-1}
    2. i*j=n,那么此时的答案等于n的头i位组成的数lim减去10^{i-1},如果lim循环j次小于limit的话,答案再加1
      然后容斥掉循环节长度为I的因数(不包括自己)所构成的数

    但是这样做要写记忆化搜索,考试的时候脑抽了,导致大数据出错……

    时间复杂度(O(Tlg v^2))

    #include<bits/stdc++.h>
    #define co const
    template<class T>T read(){
    	T data=0,w=1;char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    	for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    	return data*w;
    }
    template<class T>T read(T&x){
    	return x=read<T>();
    }
    typedef unsigned long long ll;
    using namespace std;
    
    ll fpow(ll x,int k){
    	ll re=1;
    	for(;k;k>>=1,x*=x)
    		if(k&1) re*=x;
    	return re;
    }
    ll rep(ll x,int k){
    	ll m=fpow(10,log(x)/log(10)+1e-9+1),re=0;
    	while(k--) re=re*m+x;
    	return re;
    }
    ll f[10][20];
    ll solve(ll limit){
    	if(limit==0) return 0;
    	int n=log(limit)/log(10)+1e-9+1;
    	memset(f,0,sizeof f);
    	ll ans=0;
    	for(int i=1;i<=n>>1;++i)
    		for(int j=2;j<=n/i;++j){
    			if(i*j<n){
    				f[i][j]+=9*fpow(10,i-1);
    				for(int k=1;k<i;++k)if(i%k==0){
    					f[i][j]-=f[k][i*j/k];
    				}
    				ans+=f[i][j];
    				continue;
    			}
    			assert(i*j==n);
    			ll lim=limit/fpow(10,i*(j-1));
    			assert(int(log(lim)/log(10)+1e-9)+1==i);
    			f[i][j]+=lim-fpow(10,i-1);
    			if(rep(lim,j)<=limit) ++f[i][j];
    			for(int k=1;k<i;++k)if(i%k==0){
    				f[i][j]-=f[k][n/k];
    			}
    			ans+=f[i][j];
    		}
    	return ans;
    }
    int main(){
    	freopen("circulate.in","r",stdin),freopen("circulate.out","w",stdout);
    	for(int T=read<int>();T--;){
    		ll L=read<ll>(),R=read<ll>(); 
    		printf("%llu
    ",solve(R)-solve(L-1));
    	}
    	return 0;
    }
    

    std

    【题目模型】
    T 组询问,询问[L,R]内有多少个数字是循环的。
    【算法一】
    枚举范围内的每一个数字,再判定其是否循环。期望得分 30%。
    【算法二】
    先预处理出[1,MAX{R}]内的所有循环数,排序。对于每次询问,二分出询问的边界,
    然后即可计算出满足的数字个数了。期望得分为 70%。
    【算法三】
    对于[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 <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    typedef long long s64;
    
    inline s64 getint()
    {
    	static char c;
    	while ((c = getchar()) < '0' || c > '9');
    
    	s64 res = c - '0';
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = res * 10 + c - '0';
    	return res;
    }
    
    const int MaxNL = 19;
    
    s64 tab[MaxNL];
    s64 orz[MaxNL];
    
    inline int countNumber(s64 x)
    {
    	int l = 0, d[MaxNL];
    	while (x > 0)
    		d[++l] = x % 10, x /= 10;
    
    	int ans = 0;
    	for (int i = 1; i < l; ++i)
    		ans += tab[i];
    	s64 s = 1;
    	for (int j = 1; j < l; ++j, s *= 10)
    		if (l % j == 0)
    		{
    			s64 t = 0;
    			for (int i = l; i > l - j; --i)
    				t = t * 10 + d[i];
    
    			s64 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;
    }
    
    int main()
    {
    	freopen("circulate.in", "r", stdin);
    	freopen("circulate.out", "w", stdout);
    
    	for (int i = 1; i < MaxNL; ++i)
    	{
    		s64 s = 9;
    		for (int j = 1; j < i; ++j, s *= 10)
    			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];
    			}
    	}
    
    	int nT = getint();
    	while (nT--)
    	{
    		s64 qL = getint(), qR = getint();
    		int ans = countNumber(qR) - countNumber(qL - 1);
    		printf("%d
    ", ans);
    	}
    
    	fclose(stdin);
    	fclose(stdout);
    	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
    【样例解释】
    将高度为 6 和高度为 2 的山轰平,使用第一款轰山炮,各只需 1 次即可轰平。高度为 8
    的山最少需要 2 次,弹药不够用。所以最多轰平 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。

    std

    显然,轰平高度低的山的次数总是不多于轰平高度高的山的次数,所以优先轰高度低
    的山总是最优的。

    20%:从低到高枚举每一座可以被轰的山。对于每座山,不同高度时可以选用的大炮是不同的。那么对于当前高度 H,选出 Di 最大的可以用的大炮,把山的高度 H 降至 H-Di,接着再类似做,直到 H 被降至 0 或 0 以下。总效率 O(NMH)。
    考虑所有的大炮,假如存在大炮 i,j 满足:Ai>=Aj,Di>=Dj,那么在任何情况下,大炮 i 都是比大炮 j 要优的。大炮 j 是没用的。
    舍去所有没用的大炮后{你可以用排序+单调队列做(O(MlogM)),也可以排序+枚举判定是否舍弃(O(M^2))},我们得到了剩下有用的大炮。这些大炮总是 Ai 递增,Di 递减的。
    这样,剩下的 S 门大炮就将高度分成了 S 个区间。每个区间的 Di 是固定的,即假如高度 H 处于第 i 个区间中,那么发射一次最多降至 H-Di。

    50%:从低到高枚举每一座可以被轰的山。对于每座山,求出当前高度 H 所在区间 i,求出下一个区间的最高高度 A[i-1],将 H 降至 H-t*Di,满足 H-t*Di 刚好<=A[i-1](即
    H-(t-1)*Di>A[i-1]且 H-t*Di<=A[i-1]),接着再类似做,直到 H 被降至 0 或 0 以下。寻找所在区间 i 可以使用二分查找(O(logM)),不过由于高度递减,所以通过线性扫描可以做到平摊(O(1))。
    总效率 O(NMlogM)或 O(NM)。

    80%:由于 H 的范围很小,所以可以先预处理出轰平所有高度的山所需要的最少发射次数F[H]。从小到大枚举 H,求出其所在区间 i,那么有递推关系 F[H]=F[H-Di]+1。寻找所在区间 i 可以使用二分,由于高度递增,线性扫描也可以。
    之后对于每座可以被轰的山,都可以用 O(1)的时间得到轰平所需的最少发射次数。
    总效率 O(N+MlogM+H)。

    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)的时间得到轰平所需的最少发射次数。
    总效率 O(N+MlogM+MD)。

    program cannon;
    const
            MaxHashT=1000008;
            MaxN=250003;
            MaxM=503;
            MaxMMaxD=250003;
    var
            Table:array[-2..MaxHashT] of int64;
            f:array[-2..MaxHashT] of int64;
    
            a,h:array[-2..MaxN] of int64;
            d:array[-2..MaxM] of longint;
            k:int64;
            n,m,ans:longint;
    
    procedure open(s:string);
    begin
            assign(input,s+'.in');   reset(input);
            assign(output,s+'.out');  rewrite(output);
    end;
    
    procedure close_;
    begin
            close(input); close(output);
    end;
    
    
    procedure swap(var a,b:longint);
    var
            t:longint;
    begin
            t:=a; a:=b; b:=t;
    end;
    procedure swap(var a,b:int64);
    var
            t:int64;
    begin
            t:=a; a:=b; b:=t;
    end;
    
    function min(a,b:int64):int64;
    begin
            if a<b then exit(a);
            exit(b);
    end;
    
    
    procedure PutIn;
    var
            i:longint;
    begin
            readln(n,m,k);
            for i:=1 to n do read(h[n-i+1]);
            for i:=1 to m do read(a[i],d[i]);
    end;
    
    
    procedure Qsort(l,r:longint);
    var
            i,j:longint;
            m:int64;
    begin
            i:=l; j:=r;
            m:=a[(l+r)>>1];
            repeat
                    while a[i]<m do inc(i);
                    while a[j]>m do dec(j);
                    if i<=j then
                            begin
                                    swap(a[i],a[j]);
                                    swap(d[i],d[j]);
                                    inc(i); dec(j);
                            end;
            until i>j;
            if i<r then qsort(i,r);
            if l<j then qsort(l,j);
    end;
    
    procedure Del;
    var
            i,j:longint;
    begin
            for i:=1 to m do
                    for j:=1 to m do
                            if (a[i]>=a[j])and(d[i]>d[j]) then
                                    begin
                                            a[j]:=0;
                                            d[j]:=0;
                                    end;
    end;
    
    function HashT(x:int64):longint;
    var
            h:longint;
    begin
            h:=x mod MaxHashT;
            while (Table[h]<>-1)and(Table[h]<>x) do
                    begin
                            inc(h);
                            if h>MaxHashT then h:=0;
                    end;
            Table[h]:=x;
            exit(h);
    end;
    
    
    procedure MakeTable;
    var
            i,p:longint;
            y,used,j:int64;
    begin
            fillchar(Table,sizeof(Table),255);
            fillchar(f,sizeof(f),42);
            f[HashT(0)]:=0;
            a[m+1]:=a[m];
            a[m+2]:=1<<60;
            d[m+1]:=d[m];
            for i:=1 to m do
                    begin
                            j:=a[i];
                            while j>=a[i]-d[i+1] do
                                    begin
                                            if j<=a[i-1] then break;
                                            used:=(j-a[i-1]-1)div d[i]+1;
                                            y:=j-used*d[i];
                                            if y<0 then y:=0;
                                            p:=HashT(j);
                                            f[p]:=min(f[p],f[HashT(y)]+used);
                                            dec(j);
                                    end;
                    end;
    end;
    
    procedure Init;
    begin
            Del;
            Qsort(1,m);
            MakeTable;
    end;
    
    procedure Bombard;
    var
            i,j:longint;
            y,used:int64;
    begin
            j:=0;
            while d[j+1]=0 do inc(j);
            for i:=1 to n do
                    begin
                            while (a[j+1]<h[i]) do inc(j);
                            if a[m]<h[i] then
                                    begin
                                            ans:=i-1;
                                            exit;
                                    end;
                            used:=(h[i]-a[j]-1)div d[j+1]+1;
                            y:=h[i]-used*d[j+1];
                            if y<0 then y:=0;
                            used:=used+f[HashT(y)];
                            if k-used>=0 then k:=k-used
                            else
                                    begin
                                            ans:=i-1;
                                            exit;
                                    end;
                    end;
            ans:=n;
    end;
    
    procedure PutOut;
    begin
            writeln(ans,' ',k);
    end;
    
    begin
            open('cannon');
            PutIn;
            Init;
            Bombard;
            PutOut;
            close_;
    end.
    
  • 相关阅读:
    tar解压时如何去掉第一级目录并解压到指定目录?
    ubuntu下容器无法启动报错"failed to start daemon: failed to dial "/run/containerd/containerd.sock": unknown service containerd.services.namespaces.v1.Namespaces: not implemented"如何处理?
    redis用法介绍
    Map.putAll()用法
    Random,ThreadLocalRandom,SecureRandom的几点思考
    SOAPUI---使用断言
    AutoUpdater迁移到Github
    VirtualBox: linux 没有权限访问共享文件夹的问题
    MakeFIle: 解决“/bin/bash^M: bad interpreter: No such file or directory”的问题
    ubuntu 备忘录
  • 原文地址:https://www.cnblogs.com/autoint/p/10897786.html
Copyright © 2011-2022 走看看