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.
原文地址:https://www.cnblogs.com/autoint/p/10897786.html