5.29测试

骗 分 大 赛

T1 X国的军队(army.cpp)

题目大意

你分析得出,攻占一个据点\(i\),为了稳定己方士兵士气,至少需要\(B_i\)个士兵参战,战后将会有\(A_i\)个士兵阵亡。
由于不停地调谴,可用的士兵已经不多了,于是在一个据点参战且未阵亡的士兵可能会参加别的据点的战斗。
你需要计算出攻破这\(n\)个据点所需要的最少的士兵数目。
\(T\)组数据

对于前\(20\%\)的数据\(n≤9\)
对于前\(40\%\)的数据\(n≤1000\)
对于\(100\%\)的数据\(n≤100000\)\(T≤10\)\(1≤A_≤B_≤1000000000\)

Sol

不会,写了个20pts的暴力就爪巴了
但是票池会,确实是神
啊是贪心啊那没事了

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return x*f;
}
struct jd
{
    int a,b,x;
    bool operator<(const jd &X)const
    {
        return x>X.x;
    }
}e[100010];
int T,n;
signed main()
{
    freopen("army.in","r",stdin);
    freopen("army.out","w",stdout);
    T=read();
    while(T--)
    {
        n=read();
        for(int i=1;i<=n;i++)
        {
            e[i].a=read(),e[i].b=read();e[i].x=e[i].b-e[i].a;
        }
        sort(e+1,e+n+1);
        int ans=0,now=0;
        for(int i=1;i<=n;i++)
        {
            if(now<e[i].b)
            {
                ans+=e[i].b-now;
                now=e[i].b;
            }
            now-=e[i].a;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

T2 排列组合(pc.cpp)

题目大意

\(T\)组数据,每次给定\(n\),请求出下式的值,对\(10^9+7\)取模:

\[C_n^0*C_n^0\ +\ C_n^1*C_n^1\ +\ C_n^2*C_n^2\ +\ ...\ +\ C_n^n*C_n^n\ \]

对于\(30\%\)的数据,\(T≤500 , n≤10000\)
对于\(100\%\)的数据,\(T≤100000 , n≤1000000\)

Sol

学了半天组合数学,全部木大。
认真推式子我没推出来。所以写了个暴力看前\(20\)项的规律。

1 2 6 20 70 2552 924 3432 12870 48620 184756 ......

先是质因数分解,分出来没啥规律,放弃。
然后是差分后找规律,没找出来,放弃。
想了一下还是推一下式子吧。由定理可得

\[C_n^i\ =\ C_{n-1}^{i-1}\ +\ C_n^{i-1} \]

那么

\[{C_n^i}^2\ =\ {C_{n-1}^{i-1}}^2\ +\ {C_n^{i-1}}^2\ +\ 2\ *\ C_{n-1}^{i-1}\ *\ C_n^{i-1} \]

不难得出两个平方项在对每个\(C_n^i\)计算之后求和的结果都是\(f(\ n\ -\ 1\ )\)
由此大概可以得出\(f(n)=2\ *\ f(n\ -\ 1)\ +\ ?\)
于是我又照着两倍差分了一遍,还是啥也没有。数据如下:

0 2 8 30 112 420 1584 6006

上了个厕所,发了一会儿神,然后就找到规律了。
\(0=1x0/1\)
\(2=2x2/2\)
\(8=6x4/3\)
\(30=20x6/4\)
\(...\)
\(g(i)=f(i)*(2n-2)/n\)
式子有了\(f(n)=(4n-2)*f(n-1)\)
剩下的就是数论基操
注意是多测,所以预处理然后\(O(1)\)回答即可
啊是范德蒙德卷积标准款啊我sb了

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1000000007;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return x*f;
}
int ans[1000010],inv[1000010];
inline void pre()
{
    inv[1]=1;
    for(int i=2;i<=1000000;i++)
    {
        inv[i]=(-(inv[p%i]*(p/i))%p+p)%p;
    }
    ans[0]=1;
    for(int i=1;i<=1000000;i++)
    {
        ans[i]=ans[i-1]*inv[i]%p*(4*i-2)%p;
    }
    return;
}
signed main()
{
    freopen("pc.in","r",stdin);
    freopen("pc.out","w",stdout);
    pre();
    int T=read();
    while(T--)
    {
        int x=read();
        printf("%lld\n",ans[x]);
    }
    return 0;
}

T3 Dinner(dinner.cpp)

题目大意

一共\(N\)个人坐在坐在圆桌旁。
服务员拿来了\(M\)份菜单。第\(i\)个人阅读菜单并点出
自己喜欢的菜需要花费时间\(T_i\)
当一个人点完菜之后,就会把菜单传到他右手边的第一个人。
\(M\)份菜单是同时发出的,每个菜单只能同时被一个人阅读。
清儿希望知道如何分发菜单,才能让点餐的总时间花费最少呢?

对于\(20\%\)的数据,\(n≤100\)\(m≤100\)
对于\(60\%\)的数据,\(n≤10000\)\(m≤100\)
对于\(100\%\)的数据,\(n≤50000,T_i≤600\)\(m≤3000\)

Sol

\(lsw\)神写的标答树上倍增,然而我不会
两次二分:第一次二分答案,第二次用前缀和优化后二分寻找下一个发菜单的位置
\(60\)分,学校霸主一样的方法\(100\)分。
可能是用了以下的优化
. 破环成链的时候可以先试一下前面算出来的答案能否通过,不能就直接跳过,保证了运算不冗余。
. 跳到k的位置直接结束(我没听懂啥意思,学校霸主说的)
我的\(60pts\)代码

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return x*f;
}
int sum[100010],t[100010],n,m,ans=1e9,L,R;
inline int next(int st,int lim,int beg)
{
    int l=st+1,r=beg+n;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(sum[mid-1]-sum[st-1]>lim)r=mid-1;
        else l=mid;
    }
    return l;
}
inline int check(int lim,int st)
{
    int le=sum[n],ti=m;
    for(int i=st;le>0&&ti;)
    {
        ti--;
        int to=next(i,lim,st);
        le-=sum[to-1]-sum[i-1];
        i=to;
    }
    if(le>0)return 0;
    return 1;
}
inline int ord(int st)
{
    int l=L,r=R;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid,st))r=mid;
        else l=mid+1;
    }
    return l;
}
int main()
{
    freopen("dinner.in","r",stdin);
    freopen("dinner.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        t[i]=t[i+n]=read();
        sum[i]=sum[i-1]+t[i];
        L=max(L,t[i]);
    }
    R=sum[n];
    for(int i=n+1;i<=n+n;i++)sum[i]=sum[i-1]+t[i];
    for(int i=1;i<=n;i++)
    {
        ans=min(ans,ord(i));
    }
    printf("%d",ans);
    return 0;
}

学校霸主的\(100pts\)的代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
//20pts

inline int read(){
    int x=0;bool flag=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')flag=1;ch=getchar();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return flag?-x:x;
}
void file(){
    freopen("dinner.in","r",stdin);
    freopen("dinner.out","w",stdout);
}
 
#define N 100005
int a[N];
int n,m;
int mx;
int sum,s[N];
bool check(int x){
    int pr=0,sf=n+1;
    while(pr+a[sf]<=x){
        pr+=a[sf];
        int now=sf-1;
        int lsw=0;
        while(now<sf+n-1){ //枚举l 
            int l=now+1,r=sf+n-1,ans=0;
            while(l<=r){
                int mid=(l+r)>>1;
                if(s[mid]-s[now]<=x)ans=mid,l=mid+1;
                else r=mid-1;
            }
            now=ans;
            lsw++;
            if(lsw>m)break;
        }
        if(now==sf+n-1&&lsw<=m)return 1;
        sf--;
    }
    return 0;
}
int main(void){
    file();
    n=read(),m=read();
    for(int i=1;i<=n;i++)a[i]=read(),a[i+n]=a[i],mx=max(mx,a[i]),sum+=a[i];
    for(int i=1;i<=2*n;i++)s[i]=s[i-1]+a[i];
    int l=mx,r=sum,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            r=mid-1;
            ans=mid;
        }
        else l=mid+1;
    }
    int x=1;
    if(n>105)for(int i=1;i<=1000000000;i++)x+=1;
    printf("%d\n",ans);
    return 0;
}

T4 回文(pal.cpp)

题目大意

他打算搞出一个数据结构,能够快速求出这个字符串下标为\([l,r]\)的子串的回文子串个数

对于\(20\%\)的数据,保证\(|S| , T≤500\)
对于\(40\%\)的数据,保证\(|S| , T ≤5000\)
对于\(100\%\)的数据,保证\(|S| ≤5000 , T≤100000\)

Sol

考试的时候写的\(manacher\),但是挂了。非常板子拿\(40pts\)
正解在学校霸主代码的帮助下搞出来了——区间\(dp\)
\(dp[i][j]\)表示区间\([i,j]\)的答案,布尔类数组\(b[i][j]\)表示子串\([i,j]\)是否是回文
首先预处理\(dp[i][i]\)\(dp[i-1][i]\)以及\(v[i][i]\)\(v[i-1][i]\)
然后第一维枚举区间宽度,第二维枚举区间起点
转移方程\(dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]\)
注意如果当前枚举的区间是回文,则\(dp[i][j]++\)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
    return x*f;
}
int dp[5010][5010],len,T,l,r;
bool b[5010][5010];
char s[5010];
inline bool check(int L,int R)
{
    if(b[L][R])return true;
    if(s[L]==s[R]&&check(L+1,R-1))return true;
    return false;
}
inline void f()
{
    for(int i=1;i<=len;i++)
    {
        dp[i][i]=1;b[i][i]=true;dp[i-1][i]=2;
        if(s[i]==s[i-1])
        {
            b[i-1][i]=true;dp[i-1][i]=dp[i-1][i-1]+dp[i][i]+1;
        }
    }
    for(int k=3;k<=len;k++)
    {
        int i=1,j=k;
        for(;j<=len;i++,j++)
        {
            dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
            if(check(i,j))dp[i][j]++;
        }
    }
}
int main()
{
    freopen("pal.in","r",stdin);
    freopen("pal.out","w",stdout);
    scanf("%s",s+1);
    len=strlen(s+1);
    f();
    T=read();
    while(T--)
    {
        l=read();r=read();
        printf("%d\n",dp[l][r]);
    }
    return 0;
}
posted @ 2021-05-29 12:02  wwlvv  阅读(52)  评论(0)    收藏  举报