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$从大到小排序,再顺次扫过去就可以了

 代码:

#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;
}
backpack

 

T2:point

NYG的动态数点

 

【问题描述】

NYG是一个善于思考的好学生。
于是NYG想往他的背包中放序列。
某一天NYG得到了一个长度为n的序列{ai}。然后NYG说,如果对于一段区间[L, R],存在L ≤ k ≤ R使得∀i∈[L, R]都有a| 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]都有a| 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$就行了

 代码:

 

#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;
}
point

 

 

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$分也没有,亏我还写出了转移方程,实在是不应该

 代码:

#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;
}
excellent

 

转载于:https://www.cnblogs.com/Joker-Yza/p/11518001.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值