前言
这次测试\(H_{e_{r_{i_{k_{o}}}}}D^{e^{l^{t^{a^{n^{a}}}}}}\)只有110pts,日常被虐
这是个团队内部测试,出题人:Dfkuaid(T1,T2) & hyl天梦(T3}
这里是\(\mathit Dfkuaid\)写的T1 & T2题解
本题解中T2所有由于个人能力不足而无法煺得的柿子可以看我们可爱的豆腐块颓的
这是团队内部测试的题,所有题面豆腐块也放在了这里(Password:0411)
T1 悠闲的小Aik
题目背景
\(Aik \_ufoid\)最近迷上了一款塔防类游戏
题目背景理解
背景和题目关联度\(<0.001\),(\(Aik\)迷上了明囸萝卜舟)
题目描述梳理
关于关卡
给出\(n\)个关卡,且每个关卡\(i\)都有以下的参数:
- \(a_i\),通关门槛,若当前的能力值大于\(a_i\)才可以通过此关
- \(b_i\),通关奖励,通关之后\(Aik\)的能力值会加上\(b_i\)
- \(c_i\),解锁此关卡的要求,在区间\([i-c_i,i-1]\)之间的关卡只要有一关已经通过,则第\(i\)关解锁,\(c_i\)非严格递减
关于初始限制
- \(Aik\)有一个能力的初始值\(X\),且必须参加第一关
- 对于每一个时刻的\(Aik\)的能力值\(M\),满足:
关于数据范围
对于 \(70\%\) 的数据,\(n\leq2\times10^3\)
对于 \(100\%\) 的数据,\(n\leq3\times10^4\)
在任意时刻,\(Aik\)的能力值 \(M\leq10^4\)
思路
首先,这是一道DP,所以我们是肯定要煺柿子的,但是在煺柿子之前,先来进一步的看看题目
能力值
题面最最最最最显眼的一个式子往往都非常的重要,这道题也符合这个理论,
根据费马小定理得:
\[p\equiv49(\text{mod }100),q\equiv1(\text{mod }15) \]于是原同余柿即:
\[M \equiv 2 \times 50\pmod{100}=100\pmod{100} \]也就是说
\[p \equiv 0 \pmod{100} \]再看到\(M\)的范围小于10000,也就是说\(M\)实际上只有100个数(仿佛嗅到了部分暴力的气息)
煺柿子
看完了题面当中最长的同余柿,我们就要开始着手于设计状态,然后开始颓柿子力
我在这里就采用\(\mathit Dfkuaid\)的\(STD\)里的状态吧:
\(f_{i,j}\)表示当\(Aik\)能力值为\(j\)时到达第\(i\)关,通过此关后的最小步数
(注意,这里的\(i\&j\)是到达时的值,并非通关之后的值)
因为我们的这一关一定要解锁才能通关,而解锁的条件是在区间\([i-c_i,i-1]\)里有任意一关被通过,因为我们要求最短路径,所以我们就可以在这个区间里面通过任意一关,然后直接跳到第\(i\)关
我们设\(k\)来当做我们在这个区间里面选的关卡,\(k\)要满足它的\(f\)最小,于是我们可以煺得这个柿子:
\[f_{i,j}=\min\limits_{i-c_i≤k<i}\{f_{k,j-b_k}\}+1 \]再稍微解释一下\(min\)里面的东西,第一个下标\(k\)比较好理解,至于\(j-b_k\),就是到第\(k\)关时的能力值
这样直接用当前的\(j\)去减去\(b_k\),是和我们设计的状态相关——我们设计的状态的\(j\)是到达第\(i\)关时的能力值,还未通关,所以直接减去第\(k\)关的通过奖励就能得到第\(k\)关的状态
至于这个\(j\),我们可以在输入\(X\)的时候直接除100,所以\(j\)实际上只有100个,可以直接枚举
那么DP过程就是这样的:
for(R int i=1;i<=n;i++)
{
for(R int j=100;j>=a[i];j++)
{
for(R int k=i-c[i];k<i;k++)
{
f[i][j]=min(f[i][j],f[k][j-b[k]]);
}
f[i][j]+=1;
}
}
但是这个东西是\(O(100n^2)\)的,这样是只有70pts的(虽然我最一开始只骗了10pts)
于是乎我们考虑优化
优化
观察我们观察的柿子,当我们推到\(i+1\)时,有:
\[f_{i+1,j}=\min\limits_{i+1-c_{i+1}\leq k< i+1}\{f_{k,j-b_k}\}+1 \]然后我们把这两个柿子放在一起看看:
\[f_{i+1,j}=\min\limits_{i+1-c_{i+1}\leq k< i+1}\{f_{k,j-b_k}\}+1 \] \[f_{i,j}=\min\limits_{i-c_i≤k<i}\{f_{k,j-b_k}\}+1 \]誒?我们每次选\(k\)的区间貌似是有重合部分的?
然后再看题面中一个小小的点“\(c_i\)非严格递减”,这就说明我们每次选取\(k\)的区间时,左端点会平移数位,而有端点只平移一位,这确实是有重合的
可以想象一下,这个区间的长度不断地缩减,不断地右移......
而我们每次只想要的是区间\([i-c_i,i-1]\)里的最小值,如何求一个区间的最小值?\(Dfkuaid\)给了我们一个思路:单调队列
我们只需要维护一个非严格递增的单调队列即可!
但是又有一个问题:\(Aik\)到达第\(n\)关的时候可能有多种方案,也就是说到达终点时的\(j\)可能会有不同,维护一条单调队列肯定是不够的
当我们在期待\(Dfkuaid\)又会掏出什么好家伙的时候,他直呼:
Dfkuaid没说这句话,我瞎编的,我自裁
于是乎我们就可以得到最后的\(Code\)了
Code
STD
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l))
using namespace std;
const int N = 50010;
const int INF = 0x3fffffff;
struct Hurdle{
int a;
int b;
int c;
};
Hurdle h[N];
int n,x,f[N][201],q[201][N];
int frt[201],tal[201];
int main(){
mset(tal,-1);
mset(f,0x3f);
scanf("%d%d",&n,&x);
x /= 100;
for (int i = 1;i <= n;i ++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
h[i].a = a / 100;
h[i].b = b / 100;
h[i].c = c;
}
f[1][x] = 1;
q[h[1].b + x][++ tal[h[1].b + x]] = 1;
for (int i = 2;i <= n;i++){
for (int j = 100;j >= h[i].a;j --){
int nxt = j + h[i].b;
while (frt[j] <= tal[j] && q[j][frt[j]] < i - h[i].c)
frt[j] ++;
if (frt[j] <= tal[j])
f[i][j] = f[q[j][frt[j]]][j - h[q[j][frt[j]]].b] + 1;
while (frt[nxt] <= tal[nxt] && f[q[nxt][tal[nxt]]][nxt - h[q[nxt][tal[nxt]]].b] >= f[i][j])
tal[nxt] --;
q[nxt][++ tal[nxt]] = i;
}
}
int minn = INF;
for (int i = 0;i <= 100;i ++)
minn = min(minn,f[n][i]);
if (minn > n) cout << "+200";
else cout << minn;
return 0;
}
我敲的
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
using namespace std;
I void fr(int &x)
{
LL f=1;char c=getchar();
x=0;
while(c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
x*=f;
}
struct point
{
int a,b,c;
}h[50015];
int n,x,f[50015][215],q[50015][215],head[215],tail[215];const int MIXX=0x3fffffff;int minx=MIXX;
S main()
{
std::ios::sync_with_stdio(false);
memset(tail,-1,sizeof(tail));//如果这个单调队列为空,那么head一定大于tail,head初始默认是0
memset(f,0x3f,sizeof(f));
fr(n),fr(x);x/=100;
for(R int i=1;i<=n;i++) fr(h[i].a),fr(h[i].b),fr(h[i].c),h[i].a/=100,h[i].b/=100;
f[1][x]=1;//边界值
q[h[1].b+x][++tail[h[1].b+x]]=1;//第一个进入相应的单调队列
for(R int i=2;i<=n;i++)
{
for(R int j=100;j>=h[i].a;j--)
{
int nex=j+h[i].b;
while(head[j]<=tail[j] && q[j][head[j]]<i-h[i].c) head[j]++;
if(head[j]<=tail[j]) f[i][j]=f[q[j][head[j]]][j-h[q[j][head[j]]].b]+1;
while(head[nex]<=tail[nex] && f[q[nex][tail[nex]]][nex - h[q[nex][tail[nex]]].b]>=f[i][j]) tail[nex]--;
q[nex][++tail[nex]]=i;
}
}
for(R int i=0;i<=100;i++) minx = min(minx,f[n][i]);
if(minx>n)std::cout<<"+200";
else std::cout<<minx;
Heriko Deltana;
}
PS:有点意思的是:
T2 卡牌大师
实际上这是本次测试最难的一道题
我们可爱的\(Dfkuaid\)给我们讲的时候证明定理证明了一个半小时Orz
由于我组合数学基本没学,所以下面要用到的恒等式和定理我就不证明了,想看证明可以去\(Dfkuaid\)的题解
由于有点远了,在这里再放一遍题面链接:
“这是团队内部测试的题,所有题面豆腐块也放在了这里(Password:0411)”
题目背景
\(Aik\_ufoid\)通关了塔防游戏,他又开始玩另一款卡牌游戏
题目背景理解
\(Aik\)去Van卡牌游戏了(疑似公主焊接)
背景和题目关联度\(<0.114514191981019260817\)
题目描述梳理
至多 \(m\) 个 \(A\) 与至多 \(n\) 个 \(B\) 可形成的排列总数(不计空排列)
(豆腐块:这句话我越看越眼熟)
数据范围
对于\(100\%\)的数据: \(n,m\leqslant10^{18},t\leqslant10^6n\)
没错,这题基本莫得部分分,豆腐块真良心啊(真诚.jpg)
思路
既然梳理完了之后如此简单(我并不觉得)
那我们就可以直接煺柿子了(对我来说是抄/kk)
煺柿子1.0
我们考虑\(A\)的个数 & \(B\)的个数,有
\[\sum\limits_{i=0}^m\sum\limits_{j=0}^n\dbinom{i+j}{i}-1 \]那么根据这个和式也能很明显的看出来现在这个柿子的做法是\(O(nm)\)
这看似可以,但是我们可爱的\(Dfkuaid\)早就想到了这一点,怎么可能直接让我们水过去这个连部分分都没有的题呢?于是他划定的数据范围是:\(n,m≤10^{18}\)
那么这个算法铁定是不行的,那么我们就继续煺柿子罢!
煺柿子2.0
朱世杰恒等式[详见豆腐块证明]:
\[\sum\limits_{i=0}^n\dbinom{i+m}{m}=\dbinom{n+m+1}{m+1} \]于是我们就可以开始煺这个屑柿子力:
\[\begin{aligned} &\sum\limits_{i=0}^m\sum\limits_{j=0}^n\dbinom{i+j}{i}-1\\ =&\sum\limits_{i=0}^m\dbinom{i+n+1}{i+1}-1\\ =&\sum\limits_{i=1}^{m+1}\dbinom{i+n}{i}-1\\ =&\sum\limits_{i=0}^{m+1}\dbinom{i+n}{i}-\dbinom{n}{0}-1\\ =&\sum\limits_{i=0}^{m+1}\dbinom{i+n}{n}-2\\ =&\dbinom{n+m+1+1}{n+1}-2\\ =&\dbinom{n+m+2}{n+1}-2 \end{aligned} \]我们可爱又可恨的\(\sum\sum\)被削掉了,这个柿子变得简单了许多!
但是这个对于数据\(n,m\leq1e18\)来说还是太复杂了,预处理是要超限的,直接用组合数的公式也是药丸的(
啊那我们怎么办呢?可爱的\(Dfkuaid\)告诉我们说:\(“use\) \(\scr Lucas”\)
煺柿子3.0
\(Lucas\)定理详见豆腐块证明
在这里,我们设\(p\)是一个素数,显然,把\(n\)用\(p\)进制表示是唯一的:
\[n=a_0+a_1p^1+a_2p^2+a_3p^3+\cdots+a_{k-1}p^{k-1}+a_kp^k \]同理,对于\(m\):
\[m=b_0+b_1p^1+b_2p^2+b_3p^3+\cdots+b_{k-1}p^{k-1}+b_kp^k \]那么就有:
\[\dbinom{n}{m}\equiv\prod\limits_{i=0}^k\dbinom{a_i}{b_i}(\text{mod }p) \]接下来就是把\(n\&m\)进行拆解,然后按照此柿子计算即可
但是直接算还是超时的,就要线性求其阶乘 & 逆元,但是这玩意我不会,豆腐块也没怎么讲,所以我们就直接看\(Code\)罢!
Code
这里就只有STD力,我太菜了实在是敲不出来了/kk
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l))
using namespace std;
const int N = 20000010;
const int MOD = 19260817;
const int INF = 0x3fffffff;
ll t,k,n,m,tms[N],inv[N],tmsinv[N],ans,coe[2][3];
ll p[3] = {1,19260817,370979071507489};
inline void pre_c(){
tms[0] = 1,tmsinv[0] = 1,inv[1] = tmsinv[1] = 1;
for (int i = 1;i < N;i ++)
tms[i] = (tms[i - 1] * i) % MOD;
for(int i = 2;i <= MOD;i ++){
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
tmsinv[i] = tmsinv[i - 1] * inv[i] % MOD;
}
}
inline ll C(ll i,ll j){
if (i < j) return 0;
if (j == 0) return 1;
return ((tms[i] * tmsinv[j]) % MOD) * tmsinv[i - j] % MOD;
}
inline void get_coe(ll x,int rel){
int last = 2;
while (x > 0){
while (x < p[last])
last --;
coe[rel][last] = x / p[last];
x -= coe[rel][last] * p[last];
}
}
int main(){
pre_c();
scanf("%lld",&t);
while (t --){
scanf("%lld%lld",&n,&m);
ans = 1;
mset(coe,0);
get_coe(m + n + 2,0);
get_coe(m + 1,1);
for (int i = 0;i <= 2;i ++)
ans = (ans * C(coe[0][i],coe[1][i])) % MOD;
printf("%lld\n",((ans - 2) % MOD + MOD) % MOD);
}
return 0;
}
T3 沉迷游戏的小Aik
题目背景
\(Aik\_ufoid\)因为沉迷游戏,学习成绩一落千丈
我也是一落千丈誒......
哦不对,我好像本来就倒数......嘤嘤嘤
我怀疑某月考第一的 出题人 在讽刺我这个倒数第五/kk
题目背景理解
总之\(Aik\)就是非常喜欢\(\scr Van\)游戏呗
背景和题目关联度\(=0\)
题目描述梳理
题目上来给了个\(\sum\sum\)柿:
\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i}[(i,j)=1] \]其中\([(i,j)=1]\)的意思若指\(gcd(i,j)=1\),值为1,否则值为0
作为一个数学垫底的,直接把我看傻了,吓得我搜了一下这玩意怎么算
哦,原来是先算里面再算外面啊
思路
我一看!这两个年轻\(\sum\),一个从1遍历到\(n\),一个从1遍历到\(j\),真是有而\(Bear\)来啊!
他们说比试比试,我说,可以
他上来就是一个互质,一个样例114514
我突然想起这个很像欧拉函数啊,用一个欧拉函数全给防回去了
我这个传统HD功夫,讲究个点到为止,我把Code敲完了,直接就编译提交啊一气呵成
啊他突然就,偷袭我这个马上因为whk太菜而AFO的老同志,直接给我10个\(TLE\)
我当时眼泪就流下来了,我说,婷婷,你不讲武德
我说,这么经典的求欧拉函数的方法怎么能\(Time Limit Enough\)呢?
我说你不讲\(Code\),就\(200ms\)还偷袭我一下,直接卡我普通求法
他说让我好好反思,耗子尾汁,下次不要再犯这样的错误
我很不服气啊,我直接想到都是欧拉老先生搞的欧拉筛
我用传统HD功夫,指点键盘,激昂代码,誒,就改完了
我一提交啊,终于就过了啊,发现大家都比STD快啊,那两个\(\sum\)赶紧说,对不起对不起,HD老师我错了,
我原谅了他们,我说,你们要好好反思,耗子尾汁,下次不能在反客为主,不能再偷袭
我觉得啊,这\(OIer\)都得讲个\(Code\),我这里把\(Code\)放出来啊
大家都不能跟那两个年轻的\(\sum\)学啊,也不能反客为主,反客为主夺笋啊,大熊猫都出不了你家门
所以我劝你们也吸取教训,要讲Code,好好反思,耗子尾汁!
Code
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
using namespace std;
I void fr(LL &x)
{
LL f=1;char c=getchar();
x=0;
while(c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
x*=f;
}
const int MXX=1e7+5;
LL n,ans,n1,f[MXX],prime[MXX];
bool is[MXX];
I void eular(R LL n)
{
memset(is,false,sizeof(is));
f[1]=1;
for(R LL i=2;i<=n;i++)
{
if(!is[i]) prime[++n1]=i,f[i]=i-1;
for(R LL j=1;j<=n1 && prime[j]*i<=n;j++)
{
R LL k=prime[j]*i;
is[k]=true;
if(i%prime[j]==0)
{
f[k]=f[i]*prime[j];
break;
}
else f[k]=f[i]*f[prime[j]];
}
}
}
S main()
{
std::ios::sync_with_stdio(false);
fr(n);
eular(n);
for(R LL i=1;i<=n;i++)
{
//std::cout<<k<<endl;
ans+=f[i];
}
std::cout<<ans<<endl;
Heriko Deltana;
}
《关于大家都比STD快这件事》
End
总的来说还是我太菜了,我也快\(AFO\)了,纪念纪念吧......