总结
今天的一切都挺正常的,只不过旁边的LSB的电脑坏了,于是腐王就离去了。
先看第一题。
怎么感觉这题有些难?好像我以前见过的一道神仙题今天你AK了吗,于是我头皮发麻,转到T2。
觉得这题像是图论耶,于是在纸上画出了矛盾关系,但似乎求不到答案啊!这题应该是数论或DP,然后我就转到T3。
感觉T3是给每一个数划分“势力范围”,即记录下它为最大值的最大区间的 l 和 r ,它为最小值的最大区间的 l 和 r。于是我就认为T3最水,然后就死磕T3了……曾一度脑子“短路”认为自己想出了正解,然而最终仍然毫无头绪,只好回到T1。
这时,我才发现T1是如此的水,立刻想出了正解。
T2貌似是状压DP,我列出了状态转移方程,是
O
(
k
⋅
2
n
)
O(k\cdot 2^n)
O(k⋅2n)。
由于我看错题意,以为k最大是
n
2
n^2
n2的,所以我认为T2状压大概有60分,然后就开始敲T1和T2了。
最后1小时我在无助中挣扎:T2的状压DP怎么搞都不对啊!怎么样例一直输出6!!!
最后1分钟,我终于打算交T2代码了,结果OJ卡了整整1.5分钟!
在此,强烈谴责那些故意卡OJ的人!!!
题解
T1
前言1:这是我比赛时所用的方法。
前言2:这道题目求的应该是第k小,而不是第k大。
先把3的次方数写出来,得
1
,
3
,
9
,
27
,
81
,
…
1,3,9,27,81,\ldots
1,3,9,27,81,…,接着就可以发现一个显而易见的规律了——每一个数都大于前面所有数的和。
于是我们不妨从小到大列举出它们的排列:
3
0
3
1
3
1
+
3
0
3
2
3
2
+
3
0
3
2
+
3
1
3
2
+
3
1
+
3
0
3
3
3
3
+
3
0
3
3
+
3
1
3
3
+
3
1
+
3
0
3
3
+
3
2
3
3
+
3
2
+
3
0
3
3
+
3
2
+
3
1
3
3
+
3
2
+
3
1
+
3
0
3
4
⋯
3^0\\ 3^1\\ 3^1+3^0\\ 3^2\\ 3^2+3^0\\ 3^2+3^1\\ 3^2+3^1+3^0\\ 3^3\\ 3^3+3^0\\ 3^3+3^1\\ 3^3+3^1+3^0\\ 3^3+3^2\\ 3^3+3^2+3^0\\ 3^3+3^2+3^1\\ 3^3+3^2+3^1+3^0\\ 3^4\\ \cdots
303131+303232+3032+3132+31+303333+3033+3133+31+3033+3233+32+3033+32+3133+32+31+3034⋯
然后就可以发现一个神奇的规律了:
以
3
x
3^x
3x为开头的排列的总数等于前面以
3
0
,
3
1
,
3
,
…
3
x
−
2
,
3
x
−
1
3^0,3^1,3,\ldots 3^{x-2},3^{x-1}
30,31,3,…3x−2,3x−1开头的所有排列的总数,为
2
x
2^x
2x,包括前面所有的即为
2
x
+
1
−
1
2^{x+1}-1
2x+1−1。且去掉开头
3
x
3^x
3x后可以发现它们是完全相等的。
因此我们可以每次将k减去
2
i
2^i
2i,(其中
2
i
≤
k
,
2
i
+
1
>
k
2^i\leq k,2^{i+1}>k
2i≤k,2i+1>k),再把答案加上
3
i
−
1
3^{i-1}
3i−1。
下面讲一种DL们的神奇的优化:
把k转换成二进制,再把这一个01串当成3进制数转回十进制,接着输出。
T2
我想到用状压DP。
设
f
i
,
j
f_{i,j}
fi,j表示矛盾了 i 条关系,已选择的人状态表示为 j 的方案数。
我们就可以用
f
i
,
j
f_{i,j}
fi,j更新
f
i
+
(
j
,
t
)
,
j
&
2
t
−
1
f_{i+(j,t),j \And 2^{t-1}}
fi+(j,t),j&2t−1,其中
1
≤
t
≤
n
1\leq t\leq n
1≤t≤n,
(
x
,
y
)
(x,y)
(x,y)表示应该排在第y个人后面的人有多少个在x中,也就是违背了y的多少条关系。
但是如果这样暴力做,时间复杂度是
O
(
2
n
n
2
k
)
O(2^nn^2k)
O(2nn2k)的,你说这能不能过(〃‘▽’〃)
因此我们需要优化(废话)。不妨定义
b
i
t
i
bit_i
biti表示 i 的二进制中有几个1,这个显然可以预处理出来。个人感觉最简单的方法就是:
for(i=1;i<1<<n;i++) bit[i]=bit[i^i&-i]+1;
接着再定义
b
i
b_i
bi表示第 i 个人的矛盾关系压缩后的状态。比如有矛盾关系1 2和1 5,那么
b
1
=
(
10010
)
2
b_1=(10010)_2
b1=(10010)2。用这两个数组优化DP就可以把时间复杂度优化到
O
(
2
n
n
k
)
O(2^nnk)
O(2nnk)了。
这题相当难调,我搞了半天才发现是细节错误:循环本是
1
→
2
n
−
1
1\to2^n-1
1→2n−1的,我用一个变量存
2
n
−
1
2^n-1
2n−1,循环时又把这个变量减一!就这个东西,把我调得死去活来。
T3
比赛时只想出了30分的做法。感觉要用线段树等数据结构维护,但是想不出正解。
可以用分治来做。定义solve(l,r)
表示区间[l…r]的所有最大值*最小值的和。
由于是分治,我们要求出一个
m
i
d
=
⌊
l
+
r
2
⌋
mid=\left\lfloor\cfrac{l+r}{2}\right\rfloor
mid=⌊2l+r⌋。考虑子区间[a…b]:
- 当
b
≤
m
i
d
b\leq mid
b≤mid时,直接调用
solve(a,b)
就可以了; - 当
a
>
m
i
d
a>mid
a>mid时,和上面一样,也是直接调用
solve(a,b)
; - 当 a ≤ m i d ⋀ b > m i d a\leq mid\bigwedge b>mid a≤mid⋀b>mid时,子区间[a…b]是跨越mid的,这种情况无法分治,需要直接处理。
我们不妨从
m
i
d
→
l
mid\to l
mid→l方向取一点
a
a
a,表示区间的左端点。在往左扫的过程中,记录区间[mid…a]中的最大值
m
a
x
l
maxl
maxl和最小值
m
i
n
l
minl
minl。
另外在区间(mid…r]中寻找两个点:
- 第一个小于 m a x l maxl maxl的位置,记为u;
- 第一个大于 m i n l minl minl的位置,记为v。
这里只讨论 u < v u<v u<v的情况, u > v u>v u>v的情况同理(以下情况中,a是固定的子区间左端点):
- 对于右端点b在区间(mid…u)中的情况,它的最大值是 m a x l maxl maxl,最小值是 m i n l minl minl,所以对答案的贡献就是 ( u − m i d ) × m a x l × m i n l (u-mid)\times maxl\times minl (u−mid)×maxl×minl
- 对于右端点b在区间[u…v)中的情况,它的最大值还是 m a x l maxl maxl,但是最小值是不确定的。可以预处理数组 p n = min m i d < i ≤ n a i p_n=\min_{mid<i\leq n}a_i pn=minmid<i≤nai,对答案的贡献就为区间[u…v]的最小值之和乘上 m a x l maxl maxl。最小值之和可以用前缀和预处理出来。
- 对于右端点b在区间[v…r]中的情况,最大值最小值都不确定,就类似地定义数组 q n = max m i d < i ≤ n a i q_n=\max_{mid<i\leq n}a_i qn=maxmid<i≤nai,同时维护前缀和记录区间内 p i × q i p_i\times q_i pi×qi的和。
这样子就可以 O ( n log 2 n ) O(n\log_2n) O(nlog2n)过这道题目了。
CODE
这次的题目细节比较多,可以对一下标程。
T1
#include<cstdio>
using namespace std;
long long three[40];
int main()
{
long long n,k,i,j,ans;
scanf("%lld",&n);
for(i=1,j=0;j<=35;i*=3,j++) three[j]=i;
while(n--)
{
scanf("%lld",&k),ans=0;
for(i=1ll<<35,j=35;i>0;i>>=1,j--)
if(k>=i)
k-=i,ans+=three[j];
printf("%lld\n",ans);
}
return 0;
}
T2
#include<cstdio>
using namespace std;
#define mod 1000000007
#define ll long long
#define M 2097152
#define N 21
int i,j,k,temp,max,n,m,q,bit[M],b[N],two[N];
ll f[N][M],ans;
int main()
{
scanf("%d%d%d",&n,&m,&q);
max=(1<<n)-1;
for(i=1;i<=m;i++) scanf("%d%d",&j,&k),b[j-1]+=1<<k-1;
for(i=1;i<=max;i++) bit[i]=bit[i^i&-i]+1;
f[0][0]=1;
for(i=0;i<=q;i++)
for(j=0;j<=max;j++)
if(f[i][j])
for(k=0;k<n;k++)
if(~j&1<<k)
{
temp=i+bit[j&b[k]];
if(temp<=q)
f[temp][j|1<<k]+=f[i][j];
}
for(i=ans=0;i<=q;i++) ans=(ans+f[i][max])%mod;
printf("%lld\n",ans);
return 0;
}
T3
#include<cstdio>
using namespace std;
#define mod 1000000007
#define ll long long
#define N 500005
ll min[N],max[N],p[N],q[N],tot[N],val[N],ans;
inline ll mymin(ll x,ll y){return x<y?x:y;}
inline ll mymax(ll x,ll y){return x>y?x:y;}
void solve(int l,int r)
{
if(l==r)
{
ans=(ans+val[l]*val[l])%mod;
return;
}
int mid=l+r>>1,a,u=mid,v=mid;
ll minl=mod,maxl=0;
min[u]=mod,max[u]=tot[u]=p[u]=q[u]=0;
for(a=mid+1;a<=r;a++)
{
min[a]=mymin(min[a-1],val[a]);
max[a]=mymax(max[a-1],val[a]);
p[a]=(p[a-1]+min[a])%mod;
q[a]=(q[a-1]+max[a])%mod;
tot[a]=(tot[a-1]+min[a]*max[a])%mod;
}
for(a=mid;a>=l;a--)
{
minl=mymin(minl,val[a]);
maxl=mymax(maxl,val[a]);
while(min[u+1]>=minl&&u<r) u++;
while(max[v+1]<=maxl&&v<r) v++;
if(u<v)
{
ans=(ans+(u-mid)*maxl%mod*minl)%mod;
ans=(ans+(p[v]-p[u]+mod)*maxl)%mod;
ans=(ans+tot[r]-tot[v]+mod)%mod;
}
else
{
ans=(ans+(v-mid)*maxl%mod*minl)%mod;
ans=(ans+(q[u]-q[v]+mod)*minl)%mod;
ans=(ans+tot[r]-tot[u]+mod)%mod;
}
}
solve(l,mid);
solve(mid+1,r);
}
int main()
{
int n,i,j,k;
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%d",&val[i]);
solve(1,n);
printf("%lld\n",ans);
return 0;
}