9.13 Test——NOIP模拟赛
T1:backpack
NYG的背包
【问题描述】
NYG有一个神奇的背包,每放进去一个物品,背包的体积就会变大。
也就是说,每放进一个物品,背包会被占用一定的体积,但是紧接着背包的总体积又会增大一定的值(注意是在放入物品后背包总体积才增大)。
NYG发觉这个背包十分好用,于是不由自主地想到了一个问题。
现在给出背包初始容量V 以及n个物品,每一个物品两个值a, b,分别表示物品所占体积 和放入背包后背包增大的体积。
NYG想知道能否把所有物品装进去? 因为NYG比较老实,这么简单的问题自然装作不会做的样子。 于是他来请教你。
【输入格式】
从文件backpack.in中读入数据
输入文件包含多组数据。
第一行一个数T表示数据组数。
对于每组数据,第一行两个数n, h,接下来n行每行两个数a, b表示物品所占体积和放入背包后背包增大的体积。
【输出格式】
输出到文件backpack.out中
对于每一组数据,如果能把所有物品装进去,输出"Yes",否则输出"No"(不包含引号)
【样例输入1】
3 5
3 1
4 8
8 3
【样例输出1】
Yes
【样例输入2】
3
7 9269
21366 1233
7178 23155
16679 23729
15062 28427
939 6782
24224 9306
22778 13606
5 22367
17444 5442
16452 30236
14893 24220
31511 13634
4380 29422
7 18700
25935 4589
24962 9571
26897 14982
20822 2380
21103 12648
32006 22912
23367 20674
【样例输出2】
Yes
Yes
No
【数据规模和约定】
对于1~4个测试点,T=10, 1≤n≤9, 1≤V≤100000, 0≤a,b≤100000
对于5∼8个测试点,T=20, 1≤n≤1000, 1≤V≤100000, 0≤a,b≤100000
对于9∼12个测试点,T=5, 1≤n≤100000, 1≤V≤1000, 0≤a,b≤1000
对于13~16个测试点,T=500, 1≤n≤1000, 1≤V≤100000, 0≤a,b≤100000
对于17∼20个测试点,T=8, 1≤n≤50000, 1≤V≤100000, 0≤a,b≤100000
解析:
$Maxmercer$上课时讲的例题,记得当时我和周围的人讨论了很久,下课时我还找$Maxmercer$问过一下的来着
思路显然是贪心
肯定需要把$a<b$的先用了,才可能搞得定$a>b$的
对于$a<b$的部分,肯定是先用$a$较小的,这样才可以尽力避免出现负数的情况
问题在于$a \leqslant b$的部分应该怎样安排,这也是当时我们上课时讨论的中心
这一部分思路很巧妙
反向地看这个放东西的部分,相当于是所需空间为$b$,能增大$a$的容量,$a>b$,这样的话就变成了和前面的情况了
因此这一部分只需要按$b$从大到小排序,再顺次扫过去就可以了
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 100004; inline int read() { int ret, f=1; char c; while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1; ret=c-'0'; while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0'; return ret*f; } int T, n, s, cnt1, cnt2; struct thi{ ll a, b; }t[maxn], f[maxn]; bool cmp1(thi x, thi y) { return x.a < y.a; } bool cmp2(thi x, thi y) { return x.b > y.b; } int main() { freopen("backpack.in", "r", stdin); freopen("backpack.out", "w", stdout); T = read(); while(T--) { n = read();s = read(); cnt1 = cnt2 = 0; for(int i = 1; i <= n; ++i) { ll x = 1LL * read(), y = 1LL * read(); if(y > x) t[++cnt1] = (thi){x, y}; else f[++cnt2] = (thi){x, y}; } sort(t + 1, t + cnt1 + 1, cmp1); ll now = 1LL * s; bool fl = 0; for(int i = 1; i <= cnt1; ++i) { now -= t[i].a; if(now < 0) { fl = 1; break; } now += t[i].b; } if(!fl) { sort(f + 1, f + cnt2 + 1, cmp2); for(int i = 1; i <= cnt2; ++i) { now -= f[i].a; if(now < 0) { fl = 1; break; } now += f[i].b; } } printf("%s\n", fl? "No": "Yes"); } return 0; }
T2:point
NYG的动态数点
【问题描述】
NYG是一个善于思考的好学生。
于是NYG想往他的背包中放序列。
某一天NYG得到了一个长度为n的序列{ai}。然后NYG说,如果对于一段区间[L, R],存在L ≤ k ≤ R使得∀i∈[L, R]都有ak | ai,我们认为它有价值,价值为R − L(若不满足条件则没有价值)。
现在NYG想知道所有区间中价值最大为多少,最大价值的区间有多少个,以及这些区间分别是什么。
【输入格式】
从文件point.in中读入数据
第一行一个整数n。
第二行n个整数ai。
【输出格式】
输出到文件point.out中
第一行两个整数,num和val,分别表示价值最大的区间的个数以及最大价值。
第二行num个整数,按升序输出每一个最大价值区间的L。
【样例输入1】
5
4 6 9 3 6
【样例输出1】
1 3
2
【样例输入2】
30
15 15 3 30 9 30 27 11 5 15 20 10 25 20 30 15 30 15 25 5 10 20 7 7 16 2 7 7 28 7
【样例输出2】
1 13
9
【数据规模和约定】
对于30%的数据,1≤n≤30, 1≤ai≤32
对于60%的数据,1≤n≤3000, 1≤ai≤1024
对于80%的数据,1≤n≤300000, 1≤ai≤1048576
对于100%的数据,1≤n≤500000, 1≤ai<232
【NYG教你学数学】
题意中这一句话:
存在L≤k≤R使得∀i∈[L,R]都有ak | ai。
的含义是:
在L到R中至少有一个位置k,满足对于L到R中的所有位置i,都有ak整除ai 。
解析:
读完一遍,发现可以二分答案
$check$的时候枚举左端点,如果用线段树维护$gcd$的话,时间复杂度就是$O(Nlog^{3}N)$,显然吃不消
考虑用$st$表维护出$gcd$和$min$,这样$check$就是$O(NlogN)$的,总时间复杂度是$O(Nlog^{2}N)$的,就可过了
一开始的时候我还想多了,以为查询一段区间的$gcd$必须把区间拆分成几个不相交的区间,结果发现和普通的$st$表一样,给首尾两段预处理好的$gcd$再取一次$gcd$就行了
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 500004; template<class T> inline void read(T &ret) { T f=1; char c; while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1; ret=c-'0'; while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0'; ret *= f; } int n, lg[maxn], ans[maxn], val, cnt; ll a[maxn]; ll gc[25][maxn], mn[25][maxn]; struct pr{ ll fir, sec; }; inline ll gcd(ll x, ll y) { return y == 0? x: gcd(y, x % y); } inline pr getpr(int x, int len) { pr ret; ret.fir = gcd(gc[lg[len]][x], gc[lg[len]][x+len-(1<<lg[len])]); ret.sec = min(mn[lg[len]][x], mn[lg[len]][x+len-(1<<lg[len])]); return ret; } inline bool check(int x) { pr y; for(register int i = 1; i <= n - x + 1; ++i) { y = getpr(i, x); if(y.fir == y.sec) return 1; } return 0; } void work() { pr y; for(register int i = 1; i <= n - val + 1; ++i) { y = getpr(i, val); if(y.fir == y.sec) ans[++cnt] = i; } } int main() { freopen("point.in", "r", stdin); freopen("point.out", "w", stdout); read(n); lg[0] = -1; for(register int i = 1; i <= n; ++i) { read(a[i]); lg[i] = lg[i>>1] + 1; gc[0][i] = mn[0][i] = a[i]; } for(register int j = 1; j <= lg[n]; ++j) for(register int i = 1; i <= n - (1<<j) + 1; ++i) { gc[j][i] = gcd(gc[j-1][i], gc[j-1][i+(1<<(j-1))]); mn[j][i] = min(mn[j-1][i], mn[j-1][i+(1<<(j-1))]); } register int l = 1, r = n, mid; while(l <= r) { mid = (l + r)>>1; if(check(mid)) val = mid, l = mid + 1; else r = mid - 1; } work(); printf("%d %d\n", cnt, val - 1); for(register int i = 1; i <= cnt; ++i) printf("%d ", ans[i]); return 0; }
T3:excellent
NYG的序列拆分
【问题描述】
因为NYG很老实,于是他又对放入背包的序列开始了思考。
由于NYG很擅长序列问题,他的一大爱好就是对这些序列进行拆分。
一个长为n的正整数序列A,对1≤i≤n都有Ai∈[l, r],NYG定义它的一个拆分为一个长为k的序列S,满足:
1. S1 = 1
2. Sk = n +1
3. Si < Si+1,1≤i<k
NYG认为,一个拆分是优秀(高贵)的,当且仅当对于每一个i, 1≤i<k,A中的元素$A_{S _{i}}$, $A_{S _{i}+1}$ . . . , $A_{S _{i+1}-1}$构成等比数列。
给出n, l, r, NYG需要你求出所有可能的(r − l +1)n个序列的优秀拆分的个数总和。由于答案可能很大,输出对109+7取模。
NYG觉得这样的拆分实在是太多了,所以就把任务扔给了你。
【输入格式】
从文件excellent.in中读入数据
第一行一个整数T, 表示数据组数。
接下来T行每行三个整数n, l, r, 意义如上所述。
【输出格式】
输出到文件excellent.out中
输出共T行,每行一个整数表示答案。
【样例输入1】
4
1 1 2
10 6 6
3 1 4
100 1000 100000
【样例输出1】
2
512
198
540522901
【数据规模和约定】
保证T≤10, 1≤l≤r≤107, 1≤n≤1018。
我们用$\sum$ r表示一个测试点中所有数据的r的总和,则 $\sum$ r ≤ 107。
各个测试点还满足如下约束:
(表略)
【NYG教你学数学】
如果你不熟悉等比数列,这里给出它的定义:
等比数列是指从第2项起,每一项与它的前一项的比值等于同一个常数的一种数列。特别地,这里我们认为一个数构成的数列也是等比数列。
如1,2,4,8是等比数列,3,3,3是等比数列,1是等比数列,9,3,1是等比数列,而1,3,5不是等比数列。
解析:
$DP$是很显然的事情
设$cnt[i]$表示长度为$i$的公比不为$1$的等比数列个数,注意这里$cnt[1] = r - l + 1$
设$dp[i]$为长度为$i$的序列拆分方案数, 并且有$dp[0] = 1$
设$s[i]$为$dp$的前缀和,即$s[i] = \sum_{j=1}^{i}dp[j]$
因此我们有转移方程:
$dp[i] = (r - l + 1) * s[i-2] + \sum_{j=1}^{i}dp[i-j] * cnt[j]$
其中$(r - l + 1) * s[i-2]$是在统计公比为1时的方案数
这个式子我在考试时是写出来了的,但是我并不会处理$cnt$数组,而且我并没有考虑公比为分数(包括公比小于$1$时)的情况,于是就连前面点都$WA$了
显然这个$dp$方程是$O(k*N)$的, 其中$k$表示最大的满足$cnt[k] > 0$时的$k$, 即$cnt[k] > 0$,$cnt[k+1] = 0$,可以大概想到这个$k$不会太大
考虑到$n$的规模是$10^{18}$,以及长得像这样的转移方程,可以大概猜到需要矩阵加速
那么先构造矩阵
设矩阵$A =\begin{bmatrix}dp[i-1] & dp[i-2] & ... & dp[i-k] & s[i-2]\end{bmatrix}$
设矩阵$C =\begin{bmatrix}dp[i] & dp[i-1] & ... & dp[i-k+1] & s[i-1]\end{bmatrix}$
那么我们就是需要构造一个矩阵$B$, 使得$A*B = C$
结合$dp[i]$的转移方程,可以构造出$B$矩阵的第一列
$B =\begin{bmatrix} cnt[1]& ...\\ cnt[2]&... \\ ... & ... \\ cnt[k] &... \\ r-l+1 & ...\end{bmatrix}$
$dp[i-1]$到$dp[i-k+1]$相当于是整体右移了一位,中间部分的矩阵也可以很快构造出来
$B=\begin{bmatrix}cnt[1] & 1 & 0 & 0 & ... & 0 & 0 & ...\\ cnt[2] & 0 & 1 & 0 & ... & 0 & 0 & ...\\ ... & ... & ... & ... & ... & ... & ... & ...\\ cnt[k-1] & 0 & 0 & 0 & ... & 0 & 1 & ...\\ cnt[k] & 0 & 0 & 0 & ... & 0 & 0 & ...\\ r-l+1 & 0 & 0 & 0 & ... & 0 & 0 & ...\end{bmatrix}$
其中$1$是在斜线方向上是呈一条直线的
现在$B$还差最后一列,也就是把$s[i-2]$推至$s[i-1]$,因为$s[i-1] = s[i-2]+dp[i-1]$,因此最后一行也很容易构造出来,构造完成的矩阵$B$:
$B=\begin{bmatrix}cnt[1] & 1 & 0 & 0 & ... & 0 & 0 & 1\\ cnt[2] & 0 & 1 & 0 & ... & 0 & 0 & 0\\ ... & ... & ... & ... & ... & ... & ... & ...\\ cnt[k-1] & 0 & 0 & 0 & ... & 0 & 1 & 0\\ cnt[k] & 0 & 0 & 0 & ... & 0 & 0 & 0\\ r-l+1 & 0 & 0 & 0 & ... & 0 & 0 & 1\end{bmatrix}$
矩乘没有交换律, 对于矩阵$C$,若用$C(i, j)$表示$C$矩阵中第$i$行第$j$列的数,则有:
$C(i, j) = \sum_{p=0}^{k}A(i, p) * B(p, j)$
初始化$A$矩阵:
$A = \begin{bmatrix}1 & 0 & ... & 0\end{bmatrix}$
答案矩阵$C = A * (B^{n})$, 写一个矩阵快速幂就行了
最后我们要的答案就是$ans = C(0, 0)$
矩阵加速的部分就是这样,题解里没有,可能是出题人认为这部分太简单了吧。但我还是搞了好久,我太菜了
菜是原罪
对于预处理$cnt$数组的部分,题解还是说的比较清楚了,对等比数列的等式的变形与处理方法都比较神仙,对着$std$也能够理解, 但我考试时是绝不可能想出来的,但即便想不出来,就枚举首项与第二项,以确定公差,这样暴力地处理$cnt$数组, 都可以拿$50 \sim 70$分,结果我最后$1$分也没有,亏我还写出了转移方程,实在是不应该
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> #include<algorithm> #include<cmath> using namespace std; typedef long long ll; const int mod = 1000000007, mx = 25; int T; ll n; int l, r; struct Matrix{ ll a[30][30]; }dw, dw0; Matrix operator * (Matrix x, Matrix y) { Matrix ret = dw0; for(int i = 0; i <= mx; ++i) for(int j = 0; j <= mx; ++j) for(int k = 0; k <= mx; ++k) ret.a[i][j] = (ret.a[i][j] + (x.a[i][k] * y.a[k][j] % mod)) % mod; return ret; } Matrix Mat_qpow(Matrix x, ll y) { Matrix ret = dw; while(y) { if(y&1) ret = ret * x; x = x * x; y >>= 1; } return ret; } ll cnt[30]; int gcd(int x, int y) { return y == 0? x: gcd(y, x % y); } ll work() { int R = sqrt((double)r + 0.5) + 1; for(int i = 1; i <= mx; ++i) cnt[i] = 0; cnt[1] = (ll)r - l + 1; cnt[2] = (ll)(r - l + 1) * 1LL * (r - l) % mod; for(int i = 1; i <= R; ++i) for(int j = 1; j < i; ++j) if(gcd(i, j) == 1) { int x = i, y = j; for(int k = 2; x * 1LL * i <= (ll)r; ++k) { x *= i; y *= j; cnt[k+1] += (ll)max(0, r / x - (l - 1) / y); } } for(int i = 3; i <= mx; ++i) cnt[i] = (cnt[i] << 1) % mod; Matrix A = dw0, ans = dw0; for(int i = 1; i <= mx; ++i) A.a[i-1][0] = 1LL * cnt[i]; for(int i = 1; i < mx; ++i) A.a[i-1][i] = 1LL; A.a[mx][0] = (ll)r - l + 1; A.a[mx][mx] = A.a[0][mx] = 1LL; ans.a[0][0] = 1LL; return (ans * Mat_qpow(A, n)).a[0][0]; } int main() { freopen("excellent.in", "r", stdin); freopen("excellent.out", "w", stdout); scanf("%d", &T); for(int i = 0; i < 30; ++i) dw.a[i][i] = 1LL; while(T--) { scanf("%lld%d%d", &n, &l, &r); printf("%lld\n", work()); } return 0; }