序列
【问题描述】
作为一名火星人,你为了占领地球,需要想方设法使地球人失去信心。现在你获得了一项能力,控制今后 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分为两种情况:
- i*j<n,那么此时的答案=9*10^{i-1}
- 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.