【垫底模拟】番外篇:【LGR-165-Div.2】洛谷 NOIP 2023 模拟赛

感觉没啥细节吧,就是四道题都挺无聊的。——洛谷比赛审核

T1 种树:贪心策略

题目链接

折叠题干

种树

题目背景

小 Rf 不是很喜欢种花,但他喜欢种树。

题目描述

路边有 \(n\) 棵树,每棵树的 高度 均为正整数,记作 \(p_1, p_2 \dots p_n\)

定义一棵树的 宽度 为它高度的正因数个数,这些树能覆盖的距离为它们宽度的乘积,你想请你的朋友们来乘凉,但你发现这些树能覆盖的距离不够多。

于是你买了总量为 \(w\) 单位的神奇化肥。你可以施若干次肥,每次你可以使用 \(k\) 单位化肥(要求 \(k\) 必须为当前化肥量的正因数),让任意一棵树的高度乘上 \(k\),同时你剩余的化肥量也会除以 \(k\)。每次施肥的树可任意选择,且每次施肥选择的树不需相同。

你需要最大化这些树所能覆盖的距离,并输出这个最大距离。答案对 \(998244353\) 取模。

输入格式

从标准输入中读入数据。

第一行,两个正整数 \(n\)\(w\)

第二行 \(n\) 个正整数 \(p_1, p_2 \dots p_n\)

输出格式

输出到标准输出。

仅一行一个整数,代表答案对 \(998244353\) 取模后的结果。

样例 #1

样例输入 #1

3 60
8 243 250

样例输出 #1

2304

提示

【样例 1 解释】

  • 第一次施肥,向第一棵树施 \(15\) 单位的肥,使其高度变成 \(120\),剩余 \(4\) 单位的化肥。
  • 第二次施肥,向第二棵树施 \(4\) 单位的肥,使其高度变成 \(972\),剩余 \(1\) 单位的化肥。
  • 这时候,三棵树的宽度分别为 \(16, 18, 8\),所能覆盖的距离为 \(2304\),为最优解。

【样例 2】

见附件下的 \(\texttt{plant/plant2.in}\)\(\texttt{plant/plant2.ans}\)


【样例 3】

见附件下的 \(\texttt{plant/plant3.in}\)\(\texttt{plant/plant3.ans}\)


【数据范围】

测试点编号 \(n \leq\) \(p_i\) \(w\) 单点分值
\(1 \sim 5\) \(10^4\) \(=1\) \(=1\) \(1\)
\(6 \sim 10\) \(10^4\) \(\leq 10^4\) \(=1\) \(3\)
\(11 \sim 15\) \(1\) \(\leq 10^4\) \(\leq 10^4\) \(3\)
\(16 \sim 20\) \(5\) \(\leq 10^4\) \(\leq 10^4\) \(6\)
\(21 \sim 25\) \(10^4\) \(\leq 10^4\) \(\leq 10^4\) \(7\)

对于 \(100 \%\) 的数据,保证 \(1 \leq n \leq 10^4\)\(1 \leq p_i \leq 10^4\)\(1 \leq w \leq 10^4\)

这个题数据还是过水了,我本来爆了 unsigned long long 了,但是取了个模竟然能过,我都没打算过()

想的是对着 \(w\) 分解质因子,对于每个质因子,设某个数含有 \(num\) 个这样的质因子,\(sum\) 个因子,那么增加的宽度应该是:\(\dfrac{sum}{num+1}\)

然后对于每个质因子考虑将其给哪个数,直接贪心。

问题在于增加的宽度大不一定最优,比如 \(4\times 5> 2\times 7\),所以增加的宽度还要算其他数的宽度乘积,但是会爆 int__128,所以靠着数据水用取模水过了。

后来听说其实可以用 \(log_2(x)+log_2(y)=log_2(xy)\) 来比较大小,于是改了一下,于是改 WA 了,不想改了,我真邪恶。

赛时代码
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
    char c=getchar();int x=0,f=1;
    while(c<48){ if(c=='-')f=-1;c=getchar();}
    while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
    return x*f;
}const int maxn=1e4+5,mod=998244353;

int n,w,p[maxn],width[maxn];
int ans=1;

il void input(){
    n=read(),w=read();
    for(rg i=1;i<=n;++i)    p[i]=read();
}

namespace Subtask_A{
    void MAIN(){
        for(rg i=1;i<=n;++i)
            for(rg j=1;j<=sqrt(p[i]);++j)
                if(!(p[i]%j)){
                    if(j!=sqrt(p[i]))   width[i]+=2;
                    else    ++width[i];
                }
        for(rg i=1;i<=n;++i)    ans=1ll*ans*width[i]%mod;
        printf("%llu\n",ans);
    }
}namespace Subtask_B{
    void MAIN(){
        p[1]=p[1]*w;
        for(rg j=1;j<=sqrt(p[1]);++j)   
            if(!(p[1]%j)){
                if(j!=sqrt(p[1]))   width[1]+=2;
                else    ++width[1];
            }
        printf("%lld\n",width[1]);
    }
}namespace Subtask_C{
    std::vector<PII> wp;
    int prim[maxn],tot;
    bool pj[maxn];
    void pre(){
	    for(int i=2;i<=maxn-1;++i)    pj[i]=true;
	    for(int i=2;i<=maxn-1;++i){
	    	if(pj[i]==true) prim[++tot]=i;
	    	for(int j=1;j<=tot && i*prim[j]<=maxn-1;++j){
	    		pj[i*prim[j]]=false;
	    		if(i%prim[j]==0)	break;
	    	}
	    }
    }
    void MAIN(){
        wp.clear();pre();int num=w;
        for(rg i=1;prim[i]<=sqrt(num);++i){
            int tot=0;
            while(!(num%prim[i]))   num/=prim[i],++tot;
            wp.emplace_back(std::make_pair(prim[i],tot));
        }
        if(pj[num]) wp.emplace_back(std::make_pair(num,1));
        for(rg i=1;i<=n;++i)
            for(rg j=1;j<=sqrt(p[i]);++j)
                if(!(p[i]%j)){
                    if(j!=sqrt(p[i]))   width[i]+=2;
                    else    ++width[i];
                }
        for(rg i=1;i<=n;++i)    ans=1ll*ans*width[i]%mod;
        for(rg i=0;i<wp.size();++i){
            for(rg k=1;k<=wp[i].second;++k){
                int pp=wp[i].first,pos=0,res=0,resl=0;
                for(rg j=1;j<=n;++j){
                    int num=p[j],wid=width[j],tot=0;
                    while(p[j]>=pp && !(num%pp))    num/=pp,++tot;
                    wid=wid/(tot+1);
                    if(resl<1ll*wid*ans/width[j])  pos=j,res=wid,resl=1ll*wid*ans/width[j];
                }
                ans+=resl;width[pos]+=res;p[pos]*=pp;
            }
        }
        printf("%llu\n",ans%mod);
    }
}

signed main(){
freopen("plant.in","r",stdin);
freopen("plant.out","w",stdout);
    input();
    if(w==1)    Subtask_A::MAIN();
    else if(n==1)   Subtask_B::MAIN();
    else    Subtask_C::MAIN();
return 0;
}

T2 汪了个汪:构造

题目链接

折叠题干

汪了个汪

题目背景

你说得对,但是小 P 在 [NOIP2022] 喵了个喵 中没有输出操作次数,获得了 \(0\) 分的好成绩。

题目描述

小 P 喜欢上了一款叫做《汪了个汪》的游戏。这个游戏有一个牌堆和一个金字塔形的棋盘,总共有 \(3\) 关。具体地,如图所示,棋盘的边长为 \(n\),第 \(i\) 行有 \(i\) 个格子,共 \(\dfrac{n(n+1)}{2}\) 个格子。

牌堆中有 \(1, 2 \dots n\) 的数字卡片 各无穷多张。你需要将这些数字卡片放到对应的棋盘格子中,每个格子恰好放一张数字卡片,要求满足棋盘的每一行的第一个元素 互不相同

小 P 发现,这个游戏的难度会随着关卡编号而增加:

  • 在第 \(0\) 关中,你不必满足其他条件。
  • 在第 \(1\) 关中,你需要保证一行内相邻的两个数互不相同,且所有由任意一行内相邻两个数组成的 无序二元组 互不相同。
  • 在第 \(2\) 关中,你需要满足第 \(1\) 关的限制,并且一行内的 所有数 必须互不相同。

例如,下面是 \(n=5\) 时可以通过第 \(2\) 关的摆放方式:

现在给定 \(n\) 与关卡编号,请你帮小 P 找出一种合适的摆放方式来通过这一关。可以证明在游戏限制下一定存在一种过关方式。

输入格式

从标准输入中读入数据。

仅一行,包含两个整数 \(n, t\),其中 \(t\) 表示关卡编号。

输出格式

输出到标准输出。

输出 \(n\) 行,第 \(i\) 行包含 \(i\) 个正整数(以空格分隔),表示棋盘第 \(i\) 行从左到右所有的数。

如果有多种合法的解,你可以输出任何一种。

样例 #1

样例输入 #1

2 1

样例输出 #1

1
2 1

样例 #2

样例输入 #2

5 2

样例输出 #2

1
2 3
4 2 5
3 5 1 4
5 4 3 1 2

提示

【说明与提示】

本题下发校验器(checker.cpp)。将 checker.cpp 编译成可执行文件 checker 后,在当前目录执行 checker woof.in woof.out woof.ans 即可校验你的答案是否符合规范。其中 woof.in 可以替换为对应输入文件名称,woof.out 可以替换为对应输出文件名称,也即构造结果。woof.ans 可以为任意文件。

返回结果说明:

  • The numbers are not in the valid range.:说明你的输出不满足每个数字都在 \(1\sim n\) 的范围内。
  • The first column does not satisfice.:说明你的输出不满足每行开头的数互不相同。
  • The pairs of numbers are not distinct.:说明你的输出不满足所有由任意一行内相邻两个数组成的无序二元组互不相同。
  • The adjacent numbers are not distinct.:说明当前关卡编号 \(\ge1\) 且你的输出不满足关卡 \(1\) 的条件。
  • The numbers in a row are not distinct.:说明当前关卡编号 \(\ge2\) 且你的输出不满足关卡 \(2\) 的条件。
  • Well done.:说明你的构造满足要求。

【数据范围】

测试点编号 \(n \leq\) \(t =\) 特殊性质
\(1\) \(6\) \(0\)
\(2\) \(6\) \(2\)
\(3 \sim 4\) \(4000\) \(2\) A
\(5 \sim 7\) \(500\) \(1\)
\(8 \sim 13\) \(500\) \(2\)
\(14 \sim 16\) \(4000\) \(1\)
\(17 \sim 20\) \(4000\) \(2\)
  • 特殊性质 A:保证 \(n + 1\)\(n + 2\) 为质数。

对于 \(100 \%\) 的数据,保证 \(1 \leq n \leq 4000\)\(t \in \{0, 1, 2\}\)

哀,怎么赛时没想出来。

考虑我们要构造的牌堆有 \(\dfrac{n\times (n-1)}{2}\) 个本质不同的横向相邻数对,而我们有的本质不同的相邻数对一共就 \(\dfrac{n\times (n-1)}{2}\),那么是刚好足够的。

而按照题目的牌堆格式分解 \(\dfrac{n\times (n-1)}{2}\),发现数对两个元素差的绝对值为 \(1\) 的有 \(n-1\) 个,差为 \(2\) 的有 \(n-2\) 个,……,差为 \(n-1\) 的有 \(1\) 个,刚刚好是牌堆的格式。

但是发现在同一行里,显然有些数对是不能同时相邻的,比如 \((3,6)\)\((2,5)\),于是考虑转一下方向,改成列的。

所以使用加减交替构造,如:

\[x\quad x+1\quad x-1\quad x+2\quad x-2\dots \]

这样就满足了条件。

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
    char c=getchar();int x=0,f=1;
    while(c<48){ if(c=='-')f=-1;c=getchar();}
    while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
    return x*f;
}const int maxn=4e3+5;

int n,t,siz[maxn],pos[maxn];
std::vector<int> v[maxn];

int main(){
freopen("woof.in","r",stdin);
freopen("woof.out","w",stdout);
    n=read(),t=read();
    for(rg i=1;i<=n;++i){
        for(rg len=0;;++len){
            int val=((len+1)>>1),x=i;
            (len&1)?x+=val:x-=val;
            if(x<1 || x>n){ pos[len]=i;break; }
            v[i].push_back(x);
        }
    }
    for(rg i=1;i<=n;++i){
        for(rg j=0;j<v[pos[i]].size();++j)  printf("%d ",v[pos[i]][j]);
        putchar('\n');
    }
getime();
return 0;
}

T3 挑战 NPC IV:诡异诈骗

题目链接

折叠题干

挑战 NPC IV

题目背景

要是什么都和 NPC 问题一样简单就好了啊。

题目描述

小 R 想为小 H 写一首情诗。他现在想出了 \(n\) 个句子,每个句子的优美度分别为 \(1, 2 \dots n\)。小 R 需要按照一定的顺序将他们组合起来,拼成一首完整的诗。换句话说,小 R 需要重新排列这 \(n\) 个句子,形成一个 \(1 \sim n\) 的排列 \(p_1, p_2\dots p_n\);第 \(i\) 行诗句的优美度就是原先第 \(p_i\) 个句子的优美度,也就是 \(p_i\)

不过,由于小 R 是位 OIer,所以他的文采并不是很稳定。他实际上会严重高估自己诗句的优美程度。若一条诗句在小 R 眼里的优美度为 \(x\),那么小 H 认为它的优美度是 \(x\) 在二进制表示下最低位的 \(1\) 的位置。其中,小 H 认为最低位的位置是 \(1\),次低位为 \(2\),以此类推。也就是说,小 H 眼中的优美度 \(f(x)\)\(1 + \log_2 \operatorname{lowbit}(x)\)

小 R 知道,小 H 拿到诗后,只会选取诗的一段来看,而她感受到的优美度是所有她读到的诗句之和。具体的,若诗有 \(n\) 句,则小 H 会在 \([1, n]\) 的所有长度 \(> 0\) 的区间中抽取一个 \([l, r]\),感受到 \(\displaystyle\sum_{l \leq i \leq r}f(p_i)\) 的优美度。小 R 为了衡量一首诗的优美度,决定将一首诗的总优美度定义为 所有情况下小 H 感受到的优美度之和

照理来说,总优美度最大的组合方式必然是最好的。遗憾的是,在小 R 的精密计算下,他发现,只有他选择总优美度恰好为 \(k\) 的情诗时,他才最有可能和小 H 走到一起。于是,小 R 想要知道,对于 \(n!\) 首本质不同的诗,第 \(k\) 小可能的总优美度是多少。两首诗本质相同当且仅当原排列 \(p_1 \dots p_n\) 相同。

小 R 发现这是一个 NPC 问题,他只好来向你求助了。由于总优美度过于巨大,你只需要帮他求出答案对 \(998244353\) 取模后的结果。

特别的,小 R 写了 \(q\) 组诗句,所以你需要分别回答他的 \(q\) 个问题。

输入格式

从标准输入中读入数据。

第一行一个正整数 \(q\),表示诗句的组数。

对于每组数据,仅一行两个正整数 \(n, k\) 描述小 R 的问题。

输出格式

输出到标准输出。

对于每组诗句,输出一行一个整数,表示第 \(k\) 小的总优美度对 \(998244353\) 取模后的结果。

样例 #1

样例输入 #1

2
3 2
3 6

样例输出 #1

13
14

样例 #2

样例输入 #2

5
4 1
4 10
4 16
4 20
4 24

样例输出 #2

32
34
36
36
38

样例 #3

样例输入 #3

10
1000000000000000000 1000000000000000000
1145141919810 19260817998244353
15 131413141314
36 93930322810121243
172 354354645654567654
666 233
1048576 2147483648
1000000007 1000000009
99824 44353
10 1

样例输出 #3

36226088
846277092
1096
12356
1239174
70731494
274614617
511280969
625722816
330

提示

【样例 1 解释】

例如,当 \(p = [1, 3, 2]\) 时,小 H 眼中每句诗的优美度分别为 \([1, 1, 2]\)。那么:

  • \(l = 1\)\(r = 1\) 时,优美度之和为 \(1\)
  • \(l = 2\)\(r = 2\) 时,优美度之和为 \(1\)
  • \(l = 3\)\(r = 3\) 时,优美度之和为 \(2\)
  • \(l = 1\)\(r = 2\) 时,优美度之和为 \(1 + 1 = 2\)
  • \(l = 2\)\(r = 3\) 时,优美度之和为 \(1 + 2 = 3\)
  • \(l = 1\)\(r = 3\) 时,优美度之和为 \(1 + 1 + 2 = 4\)

所以 \(p = [1, 3, 2]\) 的总优美度为 \(1 + 1 + 2 + 2 + 3 + 4 = 13\)

对于所有 \(3! = 6\) 个排列 \(p\),其总优美度从小到大排序后分别为 \(13, 13, 13, 13, 14, 14\),因此当 \(k = 2\)\(k = 6\) 时答案分别为 \(13\)\(14\)


【样例 2】

见附件下的 \(\verb!npc/npc2.in!\)\(\verb!npc/npc2.ans!\)


【样例 3】

见附件下的 \(\verb!npc/npc3.in!\)\(\verb!npc/npc3.ans!\)


【数据范围】

本题各测试点时间限制不相同。具体地,每个点的时间限制为 \(\max(q\times 0.5, 2)\ \rm{s}\)

测试点编号 \(n\) \(k \leq\) $q = $
\(1 \sim 3\) \(\leq 10\) \(n!\) \(2\)
\(4 \sim 8\) \(\leq 10^3\) \(2\) \(7\)
\(9 \sim 13\) \(\in [10^5, 10^6]\) \(\min(10^{18}, n!)\) \(7\)
\(14 \sim 17\) \(\leq 10^6\) \(\min(10^{18}, n!)\) \(7\)
\(18 \sim 25\) \(\leq 10^{18}\) \(\min(10^{18}, n!)\) \(10\)

对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^{18}\)\(1 \leq k \leq \min(10^{18}, n!)\)\(1 \leq q\le 10\)

这道题确实诈骗。

我们打表或者模样例可以发现,很多种排列的最后的答案都是一样的。

为什么会一样呢?因为题干中的“本质不同的排列”意味着 \(p\) 不同,并不代表 \(\sum f(p)\) 不同,而 \(f(p)\) 又是二进制上最低位 \(1\) 的位数,有极大的重复性。

在这个排列里,至少有 \(\dfrac{n}{2}\) 最低位是 \(1\),至少有 \(\dfrac{n}{2^2}\) 最低位是 \(2\),将其向上取整,那么我们可以估计出来,对于一种答案,他有多少个相同的排列。

\[\prod_{i=1}^{log_2n} \left(\dfrac{n}{2^i}\right)! \]

发现,这个东西,在 \(n=29\) 的时候,答案就已经超过了 \(10^{18}\)\(15!\times 8!\times 4!\) 约为 \(1.3×10^{18}\)),所以对于 \(n\ge 29\) 的情况,等于求 \(k=1\) 的答案,即最小值。

按照每个位置的贡献考虑,一个位置 \(i\) 被包含的次数为 \(b_i=i\times (n-i+1)\) 次,而 \(a_i=f(i)\),打乱 \(a\) 的顺序,求最小的 \(\sum_{i=1}^{n} a_i\times b_i\)

考虑让两个数的相差越大乘积越小,所以我们使得一个数组升序排序,另一个降序排序即可,严谨证明请自行搜索排序不等式,逆序<乱序<正序。

但是这个时间复杂度好像不太优秀,大概是 \(O(nlogn)\) 的。

显然我们应该需要一个纯净 \(\log\) 的时间复杂度,所以考虑按位考虑,即对 \(a\) 考虑。

因为我们排好序了,所以如果我们知道 \(a\) 的每个数 \(x\) 的数量,就可以找到其对应的 \(b\) 的区间,因为找的数 \(x\) 是相同的,所以我们所需要求的只有这个数的数量和 \(b\) 数组的和。

然后用不太简单的数学知识 \(O(1)\) 求一下 \(\sum_{i=l}^{r} b_i\)

有关平方和公式

百度百科

常见形式为:

\[\begin{aligned} \sum_{k=1}^{n} k^2&=\dfrac{n^3}{3}+\dfrac{n^2}{2}+\dfrac{n}{6}\\ &=\dfrac{n(n+1)(2n+1)}{6}\\ &=\dbinom{n+2}{3}+\dbinom{n+1}{3}\\ &=\dfrac{1}{4}\dbinom{2n+2}{3}\\ &=n\dbinom{n+1}{2}-\dbinom{n+1}{3} \end{aligned} \]

用数学归纳法证明一下:

首先 \(n=1\) 显然成立:\(\dfrac{1\times 2\times 3}{6}=1\)

\(n=x\)\(\sum_{k=1}^{n}\limits k^2=\dfrac{n(n+1)(2n+1)}{6}\) 成立,那么对于 \(n=x+1\) 来说:

\[\begin{aligned} \sum_{k=1}^{n+1} k^2&=\dfrac{n(n+1)(2n+1)}{6}+(n+1)^2\\ &=\dfrac{(n+1)(2n^2+7n+6)}{6}\\ &=\dfrac{(n+1)(n+2)(2n+3)}{6} \end{aligned} \]

证毕。

于是我们可以通过 \(\sum_{k=1}^{n}\limits k^2=\dfrac{n(n+1)(2n+1)}{6}\) 这个公式的前缀和形式得到:

\[\sum_{i=l}^{r} i^2=\dfrac{r(r+1)(2r+1)}{6}-\dfrac{l(l-1)(2l-1)}{6} \]

\[\begin{aligned} \sum_{i=l}^{r} b_i&=\sum_{i=l}^{r} i(n-i+1)\\ &=(n+1)\left(\sum_{i=l}^{r} i\right)-\sum_{i=1}^r i^2\\ &=(n+1)\dfrac{(l+r)\times (r-l+1)}{2}-\dfrac{r(r+1)(2r+1)}{6}+\dfrac{l(l-1)(2l-1)}{6} \end{aligned} \]

而我们所求的 \(b\) 还未按照顺序排序,但是这是容易的,因为和相同时一定是两数相差越大两数之积越小,即按照 \(i\) 排序两边的 \(b\) 较小。

求完了 \(b\),考虑求 \(a\) 的个数,我们随便举一个例子,这个数的二进制是:\(101010\)

我们求 \(f(x)=2\) 的数的数量,钦定第一位为 \(0\),第二位是 \(1\),那么前面四位可以是 \(0000\)\(1010\)

但是如果说这个数的二进制是 \(101000\) 呢?

我们钦定了第一位为 \(0\),第二位是 \(1\),那么前面的四位不能再包含 \(1010\) 这种可能,因为已经超过了 \(n\) 的大小了。

所以求 \(f(x)=i\) 的数的数量,应为:(n>>i)+(n>>(i-1)&1)

于是我们的时间复杂度就优化到了 \(O(logn)\)

namespace Subtask_A{
    il ll qpow(ll x,int k){
        ll res=1;
        while(k){
            if(k&1) res=res*x%mod;
            x=x*x%mod;
            k=k>>1;
        }
        return res;
    }
    int inv2=0,inv6=0;
    il int get(int x){
    // 完全平方公式
        x=x%mod;
        return 1ll*x*(x+1)%mod*(2*x+1)%mod*inv6%mod;
    }
    il int solve(int len,int l,int r){
    // 找到(l,r)区间内a对应的b的和
        if(l>r) return 0;
        int mid=(len>>1);
        if(l<=mid && mid<r) return (solve(len,l,mid)+solve(len,mid+1,r))%mod;
        if(l>mid)   l=len-l,r=len-r,swap(l,r);  // 以中点为界限分开,右边的块翻转一下,这样我们都是从l往后填了
        return (1ll*len%mod*((l+r)%mod)%mod*((r-l+1)%mod)%mod*inv2%mod-(get(r)-get(l-1)+mod)%mod+mod)%mod;
    }
    void MAIN(){
        int l=1,r=n,res=0;inv2=qpow(2,mod-2);inv6=qpow(6,mod-2);
        for(rg i=log2(n)+1;i;--i){
            int sum=(n>>i)+(n>>(i-1)&1),lnum=(sum>>1),rnum=sum-lnum;
            if(l<n-r+1) swap(lnum,rnum);
            res=mymod(res+1ll*i*solve(n+1,l,l+lnum-1)%mod);
            res=mymod(res+1ll*i*solve(n+1,r-rnum+1,r)%mod);
            l=l+lnum,r=r-rnum;
        }
        printf("%lld\n",res);
    }
}

但是到这里还没有做完,因为我们只是算出来了 \(n>29\) 的情况,对于 \(n\leq 28\) 时,即使计算 \(f\) 值不同的序列仍然有很多。

但是由于 \(n\) 值较小直接意味着每个数其最高位是 \(5\)\(2^5=32\)),即 \(a_i=f(p_i)\leq 5\),其总优美度较小(根据排序不等式写一个简单代码可得 \(n=28\) 时总优美度不超过 \(9056\)),所以我们可以直接枚举转移。

把问题抽象为填 \(a\) 的问题,设 \(f_{num1,num2,num3,num4,num5,sum}\) 表示在填了 \(num1\)\(1\)\(num2\)\(2\)\(\dots\)\(num5\)\(5\) 后,总优美度为 \(sum\)选数方案有多少个。

设当前填入了 \(t\) 个数,有转移方程:

\[\begin{aligned} &f_{0,0,0,0,0,0}=1\\ f_{num1,num2,num3,num4,num5,sum}&=f_{num1-1,num2,num3,num4,num5,sum-b_{t}}\\ &+f_{num1,num2-1,num3,num4,num5,sum-2\times b_{t}}\\ &+f_{num1,num2,num3-1,num4,num5,sum-3\times b_{t}}\\ &+f_{num1,num2,num3,num4-1,num5,sum-4\times b_{t}}\\ &+f_{num1,num2,num3,num4,num5-1,sum-5\times b_{t}} \end{aligned} \]

而这只是选数的方案,记得乘上排列的阶乘才算是排列数。

时间复杂度应小于 \(O\left(\dfrac{n^8}{2\times 5^5}\right)\),还是挺恐怖的,但是 \(n\leq 28\)

namespace Subtask_B{
    int a[maxn],b[maxn],cnt[maxn],num[6],f[16][9][5][3][2][9056];
    #define lowbit(x) (x&-x)
    void MAIN(){
        for(rg i=1;i<=n;++i)    a[i]=1+log2(lowbit(i)),b[i]=i*(n-i+1),cnt[i]=0;
        std::sort(a+1,a+1+n);std::sort(b+1,b+1+n);
        for(rg i=1;i<=n;++i)    ++cnt[a[i]];
        f[0][0][0][0][0][0]=1;
        for(num[1]=0;num[1]<=cnt[1];++num[1])
        for(num[2]=0;num[2]<=cnt[2];++num[2])
        for(num[3]=0;num[3]<=cnt[3];++num[3])
        for(num[4]=0;num[4]<=cnt[4];++num[4])
        for(num[5]=0;num[5]<=cnt[5];++num[5]){
            int t=num[1]+num[2]+num[3]+num[4]+num[5];
            if(!t)  continue;
            for(rg sum=0;sum<=9056;++sum){
                int tt=0;
                for(rg k=1;k<=5;++k){
                    if(!num[k] || sum-k*b[t]<0) continue;
                    --num[k];
                    tt+=f[num[1]][num[2]][num[3]][num[4]][num[5]][sum-k*b[t]];
                    ++num[k];
                }
                f[num[1]][num[2]][num[3]][num[4]][num[5]][sum]=tt;
            }
        }
        int fact=1,rank=0;
        for(rg i=1;i<=5;++i)    fact=fact*fac[cnt[i]];
        for(rg sum=0;sum<=9056;++sum){
            rank+=fact*f[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]][sum];
            if(rank>=k){ printf("%lld\n",sum);return; }
        }
    }
    #undef lowbit
}

完整代码:

Miku's Code
#include<bits/stdc++.h>
#define il inline
#define rg register int
#define cerr std::cerr
#define getime() cerr<<"Runtime is: "<<1.0*clock()/CLOCKS_PER_SEC<<" s"<<endl
#define endl '\n'
#define cout std::cout
#define bits(x) std::bitset<x>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned char byte;
typedef std::pair<int,int> PII;
il int Max(int x,int y){ return x<y?y:x; }
il int Min(int x,int y){ return x<y?x:y; }
il int Abs(int x){ return x<0?-x:x; }
il void swap(int &x,int &y){ int temp=x;x=y;y=temp; }
il int read(){
    char c=getchar();int x=0,f=1;
    while(c<48){ if(c=='-')f=-1;c=getchar();}
    while(c>47){ x=(x<<3)+(x<<1)+(c^48);c=getchar(); }
    return x*f;
}const int maxn=30,mod=998244353;
il ll mymod(ll x){ return x<mod?x:x-mod; }

int q,n,k,ans,fac[maxn];

namespace Subtask_A{
    il ll qpow(ll x,int k){
        ll res=1;
        while(k){
            if(k&1) res=res*x%mod;
            x=x*x%mod;
            k=k>>1;
        }
        return res;
    }
    int inv2=0,inv6=0;
    il int get(int x){
        x=x%mod;
        return 1ll*x*(x+1)%mod*(2*x+1)%mod*inv6%mod;
    }
    il int solve(int len,int l,int r){
        if(l>r) return 0;
        int mid=(len>>1);
        if(l<=mid && mid<r) return (solve(len,l,mid)+solve(len,mid+1,r))%mod;
        if(l>mid)   l=len-l,r=len-r,swap(l,r);
        return (1ll*len%mod*((l+r)%mod)%mod*((r-l+1)%mod)%mod*inv2%mod-(get(r)-get(l-1)+mod)%mod+mod)%mod;
    }
    void MAIN(){
        int l=1,r=n,res=0;inv2=qpow(2,mod-2);inv6=qpow(6,mod-2);
        for(rg i=log2(n)+1;i>=1;--i){
            int sum=(n>>i)+(n>>(i-1)&1),lnum=(sum>>1),rnum=sum-lnum;
            if(l<n-r+1) swap(lnum,rnum);
            res=mymod(res+1ll*i*solve(n+1,l,l+lnum-1)%mod);
            res=mymod(res+1ll*i*solve(n+1,r-rnum+1,r)%mod);
            l+=lnum,r-=rnum;
        }
        printf("%lld\n",res);
    }
}

namespace Subtask_B{
    int a[maxn],b[maxn],cnt[maxn],num[6],f[16][9][5][3][2][9056];
    #define lowbit(x) (x&-x)
    void MAIN(){
        for(rg i=1;i<=n;++i)    a[i]=1+log2(lowbit(i)),b[i]=i*(n-i+1),cnt[i]=0;
        std::sort(a+1,a+1+n);std::sort(b+1,b+1+n);
        for(rg i=1;i<=n;++i)    ++cnt[a[i]];
        f[0][0][0][0][0][0]=1;
        for(num[1]=0;num[1]<=cnt[1];++num[1])
        for(num[2]=0;num[2]<=cnt[2];++num[2])
        for(num[3]=0;num[3]<=cnt[3];++num[3])
        for(num[4]=0;num[4]<=cnt[4];++num[4])
        for(num[5]=0;num[5]<=cnt[5];++num[5]){
            int t=num[1]+num[2]+num[3]+num[4]+num[5];
            if(!t)  continue;
            for(rg sum=0;sum<=9056;++sum){
                int tt=0;
                for(rg k=1;k<=5;++k){
                    if(!num[k] || sum-k*b[t]<0) continue;
                    --num[k];
                    tt+=f[num[1]][num[2]][num[3]][num[4]][num[5]][sum-k*b[t]];
                    ++num[k];
                }
                f[num[1]][num[2]][num[3]][num[4]][num[5]][sum]=tt;
            }
        }
        int fact=1,rank=0;
        for(rg i=1;i<=5;++i)    fact=fact*fac[cnt[i]];
        for(rg sum=0;sum<=9056;++sum){
            rank+=fact*f[cnt[1]][cnt[2]][cnt[3]][cnt[4]][cnt[5]][sum];
            if(rank>=k){ printf("%lld\n",sum);return; }
        }
    }
    #undef lowbit
}

signed main(){
freopen("npc.in","r",stdin);
freopen("npc.out","w",stdout);
    fac[0]=1;for(rg i=1;i<=28;++i)  fac[i]=fac[i-1]*i;
    q=read();
    while(q--){
        n=read(),k=read();
        if(n>28){ Subtask_A::MAIN();continue; }
        else{ Subtask_B::MAIN();continue; }
    }
getime();
return 0;
}

T4

不会。

总结

我要开喷了。

T1 做了一个半小时,还好数据水过了,不敢保证在 NOIP 我会不会选择做一个小时啥也没做出来然后跳过。

T2 水了 10pts,这种构造题还是少出点吧。

T3 什么东西,感觉还是太菜了,没多想这道题,打了一个 next_permutation 就走了,12pts,感觉就我这个实力就算看出来 \(n>28\) 的时候相当于 \(k=1\) 也不会。

T4 让我觉得暴力很可拿,越打越假,然后又去看 T2 了,扣了一场。

总结,弱就是弱。

posted @ 2023-11-12 19:15  Sonnety  阅读(105)  评论(2编辑  收藏  举报