题目链接:https://ac.nowcoder.com/acm/contest/3003#question
此篇文章的博客园链接:https://www.cnblogs.com/lonely-wind-/p/12271400.html
emmm,没什么好说的,心理战。。。不交一发就不会知道这样确实是对的。。。

题目说明:

A.做游戏 B.排数字 C.算概率 D.数三角
(水题) (思维) (期望DP) (思维,勾股定理)

E.做计数 F.拿物品 G.判正误 H.施魔法
(思维) (贪心) (类hash) (DP)

I.建通道 J.求函数
(思维) (线段树)

A.做游戏

没什么好说的,水题,每次取相克的最小值就好了,即
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

int main(int argc, char const *argv[])
{
    ll a,b,c,x,y,z;
    cin>>a>>b>>c;
    cin>>x>>y>>z;
    ll sum=0;
    sum+=min(a,y);
    sum+=min(b,z);
    sum+=min(c,x);
    cout<<sum<<endl;
    return 0;
}

B.排数字

也没什么好说的,我们看一下就知道这是最多的情况,那么6的数量会比1多1个。。所以我们统计一下1的个数和6的个数就好了。
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=2e5+10;

char s[mac];

int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    scanf ("%s",s+1);
    int one=0,six=0;
    for (int i=1; i<=n; i++){
        if (s[i]=='1') one++;
        else if (s[i]=='6') six++;
    }
    if (six>=one+1) printf("%d\n",one);
    else {
        if (six>=2) printf("%d\n",six-1);
        else printf("0\n");
    }
    return 0;
}

C.算概率

看一下题目,应该是期望DP,然后再看一下数据范围。。。,那么应该是算法,那么我们就设这个为一个状态,那么他具体代表什么意思呢?肯定有一个是答对的题目数量,我们将它放在位置,那么很明显就代表了已经答的题目数量。

那么怎么状态转移呢?正在答的这一题可能答对也可能答错,那么也就是说
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=2e3+10;
const int mod=1e9+7;

ll dp[mac][mac],a[mac];

int main(int argc, char const *argv[])
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int n;
    cin>>n;
    for (int i=1; i<=n; i++) cin>>a[i];
    dp[0][0]=1;
    for (int i=1; i<=n; i++){
        dp[i][0]=dp[i-1][0]*((1-a[i]+mod)%mod)%mod;
        for (int j=1; j<=i; j++){
            dp[i][j]=dp[i-1][j]*((1-a[i]+mod)%mod)%mod;
            dp[i][j]+=dp[i-1][j-1]*a[i]%mod;
            dp[i][j]%=mod;
        }
    }
    for (int i=0; i<=n; i++)
        cout<<dp[n][i]<<' ';
    cout<<endl;
    return 0;
}

D.数三角

emmm,就是用到了勾股定理,一个直角三角形,我们将斜边延长一些,那么不就变成了钝角了吗。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=600;

struct node
{
    int x,y;
}pt[mac];

ll dist(int p1,int p2)
{
    ll x1=pt[p1].x,x2=pt[p2].x;
    ll y1=pt[p1].y,y2=pt[p2].y;
    ll dis=(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
    return dis;
}

int angle(ll mx,ll mid,ll mi)
{
    double s1=sqrt(mx*1.0),s2=sqrt(mid*1.0),s3=sqrt(mi*1.0);
    if (s1<s2+s3) return 1;
    return 0;
}

int ok(int id1,int id2,int id3)
{
    ll eg1=dist(id1,id2);
    ll eg2=dist(id1,id3);
    ll eg3=dist(id2,id3);
    ll mx=max(eg1,max(eg2,eg3));
    ll mi=min(eg1,min(eg2,eg3));
    ll mid=eg1+eg2+eg3-mx-mi;
    if (!angle(mx,mid,mi)) return 0;
    if (mx>mi+mid) return 1;
    return 0;
}

int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++){
        scanf ("%d%d",&pt[i].x,&pt[i].y);
    }
    int ans=0;
    for (int i=1; i<=n; i++)
        for (int j=i+1; j<=n; j++)
            for (int k=j+1; k<=n; k++){
                if (ok(i,j,k)) ans++;
            }
    printf("%d\n",ans);
    return 0;
}

E.做计数

我们做个变形就出来了,两边同时平方:那么要维持正整数,必须是平方数,即是个整数。那么我们只需要枚举1到设为然后找的因子就好了,复杂度是

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

int solve(int x)
{
    if (x==1) return 1;
    int m=sqrt(x);
    int sum=1;
    for (int i=1; i<m; i++){
        if (x%i==0) sum+=2;
    }
    return sum;
}

int main(int argc, char const *argv[])
{
    int n;
    cin>>n;
    int m=sqrt(n);
    long long ans=0;
    for (int i=1; i<=m; i++){
        ans+=solve(i*i);
    }
    cout<<ans<<endl;
    return 0;
}

F.拿物品

实际上就是按照的值贪心。至于为什么,出题人很良心的说明了:
假设物品已经被选完,此时 牛牛选择的物品 属性的价值和是, 牛可乐选择的物品 属性价值和是
如果 牛牛的物品与 牛可乐的交换,则
对于 牛牛(目标是最大化)来说会变得更优仅当 化简就能得到),
对于 牛可乐也一样。所以两人都会优先选择 最大的物品。
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=2e5+10;

struct node
{
    int val,id;
    bool operator <(const node&a)const{
        return val>a.val;
    }
}p[mac];

int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++)
        scanf ("%d",&p[i].val),p[i].id=i;
    int x;
    for (int i=1; i<=n; i++)
        scanf ("%d",&x),p[i].val+=x;
    sort(p+1,p+1+n);
    for (int i=1; i<=n; i+=2)
        printf("%d ",p[i].id);
    printf("\n");
    for (int i=2; i<=n; i+=2)
        printf("%d ",p[i].id);
    printf("\n");
    return 0;
}

G.判正误

万万没想到确是是一种取模处理的方式。。。原式取模的意义下,有概率成立,我们可以多取几个模提高真确率。。。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=2e3+10;
const ll mod=1e9+7;

ll qick(ll a,ll b)
{
    a%=mod;
    ll ans=1;
    while (b){
        if (b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll a,b,c,d,e,f,g;
    int t;
    cin>>t;
    while (t--){
        cin>>a>>b>>c>>d>>e>>f>>g;
        ll ans1=qick(a,d);
        ll ans2=qick(b,e);
        ll ans3=qick(c,f);
        ll ans=ans1+ans2+ans3;
        if (ans==g) printf("Yes\n");
        else printf("No\n");
    }
}

H.施魔法

将能量排个序,我们可以知道的是每次释放魔法一定是选择连续的一段的。那么我们就可以做一个dp: 即:

dp[m]=a[m]-a[1];
for (int i=m+1; i<=n; i++){
     for (int j=1; j<=i-m+1; j++){
          dp[i]=min(dp[i],dp[j-1]+a[i]-a[j]);
     }
}

但怎么将其化为更低的复杂度呢?我们可以发现其实很好维护它的前缀最小值的,我们只需要在每次加一个点的时候更新就好了:
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mac=3e5+10;
const ll inf=1e18;

int a[mac];
ll dp[mac];

int main()
{
    int n,m;
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        scanf ("%d",&a[i]);
    sort(a+1,a+1+n);
    memset(dp,0x3f,sizeof dp);
    ll f=-a[1];
    for (int i=m; i<=n; i++){
        dp[i]=f+a[i];
        f=min(f,dp[i-m+1]-a[i-m+2]);
    }
    printf("%lld\n",dp[n]);
    return 0;
}

I.建通道

我觉得出题人说道挺清楚的。。。首先将权值去重(权值相等的点连接代价为 0 ),设去重后有 m 个点,接下来找到最小的二进制位k ,
满足存在 的这个二进制位是 0 且存在 的这个二进制位是1,答案就是(相当于所有这位是 0 的点与 j点连边,是1 的点与 i 点连边)。
以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=2e5+10;
typedef long long ll;

int a[mac];

int deal(int x,int pw)
{
    x>>=pw;
    if ((x&1)==0) return 1;
    return 0;
}

int main(int argc, char const *argv[])
{
    int n;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++)
        scanf ("%d",&a[i]);
    sort(a+1,a+n+1);
    int p=unique(a+1,a+1+n)-a-1;
    int tmp=1;
    int m0=0,m1=0;
    ll ans=0;
    for (int i=0; i<=30; i++){
        if (i) tmp*=2;
        m0=0;m1=0;
        for (int j=1; j<=p; j++){
            if (!deal(a[j],i)) m1=1; 
            else if (deal(a[j],i)) m0=1;
            if (m0 && m1) {
                ll ans=1ll*tmp*(p-1);
                printf("%lld\n",ans);
                return 0;
            }
        }
    }
    printf ("0\n");
    return 0;
}

J.求函数

题目大意:你有n个一次函数,第i个为:,你有m次操作:
1 i k b修改为
2 l r
emmm,就这么看询问的话不好看,我们将它放出来就是:
然后我们用线段树维护就好了,不过我们需要注意合并相邻节点
我们假设t1维护, t2维护
假设t1左子树为,t1的右子树为,t2左子树为,t2右子树为
那么合并之后为

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define lc rt<<1
#define rc rt<<1|1

typedef long long ll;
const int mac=2e5+10;
const int mod=1e9+7;

struct node
{
    ll valk,valkb;
}tree[mac<<2];
int k[mac],b[mac];

void push_up(int rt)
{
    tree[rt].valk=tree[lc].valk*tree[rc].valk%mod;
    tree[rt].valkb=tree[lc].valkb*tree[rc].valk%mod+tree[rc].valkb;
    tree[rt].valkb%=mod;
}

void build(int l,int r,int rt)
{
    if (l==r){
        tree[rt].valk=k[l];
        tree[rt].valkb=b[l];
        return;
    }
    int mid=(l+r)>>1;
    build(lson);build(rson);
    push_up(rt);
}

void update(int l,int r,int rt,int pos,int valk,int valb)
{
    if (l==r){
        tree[rt].valkb=valb;
        tree[rt].valk=valk;
        return;
    }
    int mid=(l+r)>>1;
    if (pos<=mid) update(lson,pos,valk,valb);
    else update(rson,pos,valk,valb);
    push_up(rt);
}

node solve(node s1,node s2)
{
    node sum;
    sum.valk=s1.valk*s2.valk%mod;
    sum.valkb=s1.valkb*s2.valk%mod+s2.valkb;
    sum.valkb%=mod;
    return sum;
}

node query(int l,int r,int rt,int L,int R)
{
    node ans={1,0};
    if (l>=L && r<=R){
        return node{tree[rt].valk,tree[rt].valkb};
    }
    int mid=(l+r)>>1;
    if (L<=mid) ans=solve(ans,query(lson,L,R));
    if (R>mid) ans=solve(ans,query(rson,L,R));
    return ans;
}

int main(int argc, char const *argv[])
{
    int n,m;
    scanf ("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        scanf ("%d",&k[i]);
    for (int i=1; i<=n; i++) 
        scanf ("%d",&b[i]);
    build(1,n,1);
    while (m--){
        int opt,i,ck,cb,l,r;
        scanf ("%d",&opt);
        if (opt==1){
            scanf ("%d%d%d",&i,&ck,&cb);
            update(1,n,1,i,ck,cb); 
        }
        else {
            scanf ("%d%d",&l,&r);
            node ans=query(1,n,1,l,r);
            ll sum=(ans.valk+ans.valkb)%mod;
            printf("%lld\n",sum);
        }
    }
    return 0;
}