jzyz 题库 选做 及 知识小结

校oj很多题目还是很好的 考虑给他们蟹蟹题解Y

更新于:10.24

打扫卫生 题目大意:n个房间 m个同学 每个房间至少两个同学 求方案数

显然是个计数类的题目 技术类题目 真的 需要有一定数学感觉8... 头有点大奥 

然后我们考虑这道题目显然是一个组合数的问题 然后需要用到隔板法 所以在这里 我浅析一下隔板法的应用 有多学习了一个好东西

具体隔板法 对于n个有顺序的球 我们考虑在中间放入m个板 然后规定我们不能在两个球之间放两个及以上个隔板 然后两段不能放隔板 问你方案数

我们不妨考虑 在这n-1个空位置 放入m个球 那么方案数 显然是$\binom{n-1}{m}$

据说今年高联数学二试也考了个隔板法?? 还是比较好写的那个题目 到底是形如什么样的式子 可以 使用隔板法求解问题呢

我们不妨举几个例子

求解$\sum_{i=1}^{m} x_i=n$ 有多少个正整数

对于这种情况 我们可以考虑是在n个球中插入了 m-1个板 类比刚才的方法  容易得到方案数数为$\binom{n-1}{m-1}$ 

然后考虑 对于求解$\sum_{i=1}^{m} x_i=n$ 有多少个自然数解 也就是说 你可以存在$x_i=0$ 所以我们为了避免这种情况出现 

我们可以对于每一个$x_i+1$ 所以 我们的式子变成了 $\sum_{i=1}^{m} (x_i+1)=n+m$ 所以此时换元 每一个$t_i$ 就是正整数了

所以我们可以类比刚才的方法做了 然后方案数就是$\binom{n+m-1}{m-1}=\binom{n+m-1}{n}$ 

然后这道题目保证每个房间至少两个人 所以我们不妨 先将这 n*2个同学T出去 因为一定要留下来这2*n个同学 至于剩下的m-2*n个同学

我们考虑 转化成刚才我们讨论过的问题 我们除去这些2*n个人之后 我们 每个房间的人数$x_i$ 这时候就是求解 $\sum_{i=1}^{n} x_i=m-2*n$

对于这种情况 我们$x_i$ 存在 等于0的情况 我们进一步 转化成 整数情况 $\sum_{i=1}^{n} (x_i+1)=m-2*n+n$ 然后类比刚才的例子1 我们就求出了答案

所以这个题目 还有写 高精*单精 就没了

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int N=1010;
int n,m,top,b[N],p0[N],p1[N],p2[N]; 
inline void insert(int x,int *a) {
    int now=x;
    for(int i=2;i*i<=x;i++) {
        while(now%i==0) {
            now=now/i;
            ++a[i];
        }
    }
    if(now>1) ++a[now];
}
inline void mul(int x) {
    int res=0;
    for(int i=1;i<=top;i++) {
        b[i]=b[i]*x;
        b[i]+=res;
        res=b[i]/10;
        b[i]=b[i]%10;
    }
    while(res) {
        b[++top]=res;
        res=b[top]/10;
        b[top]=b[top]%10;
    }
}
int main() {
    read(n); read(m);
    if(m<2*n) {puts("0");return 0;}
    int s=m-2*n+n-1,c=m-2*n;
    for(int i=2;i<=s;i++) insert(i,p0);
    for(int i=2;i<=c;i++) insert(i,p1);
    for(int i=2;i<=s-c;i++) insert(i,p2);
    b[++top]=1;
    for(int i=2;i<=s;i++) {
        p0[i]=p0[i]-p1[i]-p2[i];
        while(p0[i]) {
            --p0[i];
            mul(i);
        }
    }
    for(int i=top;i>=1;i--) printf("%d",b[i]);
    return 0;
}
View Code

pigs 一道很有意思的网络流呢 这个建图 我着实 思考了一会 不过在Chdy的指导下 还是搞出来了 是个最大流

具体怎么建图呢 因为非常容易想到的是 我们肯定是源点向每个猪小屋连边 容量是每个猪小屋原来拥有的猪猪数量 然后 每个人向汇点连边

然后容量是每个人 原来想要购买的 猪猪的数量 那么考虑 猪小屋 和 顾客之间怎么连边 我们考虑一个事情是 每个猪小屋第一个打开他的 顾客是一定的

当前这个顾客购买完之后 我们考虑 如何将当前这个小屋的猪进行一个转移 就是指向一个接下来能打开他的顾客 所以我们对于猪小屋和顾客 

我们将每一个猪小屋 和第一个能打开他的 顾客 连边 容量是 inf 然后将当前第一个顾客 和 能够再次打开这个猪小屋的顾客连边 然后我们就实现了 猪的转移 

跑一边最大流即可 我我我我最大流还是写错了 也就是=写成==不报错呗hh 

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
const int inf=0x7fffffff;
const int N=1000010;
struct gg {
    int y,v,next;
}a[N<<1];
int n,m,x,y,z,tot,s,t,H,T,lin[N],q[N],d[N],st[N];
inline void add(int x,int y,int v) {
    a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; a[tot].v=v;
    a[++tot].y=x; a[tot].next=lin[y]; lin[y]=tot; a[tot].v=0;
}
inline bool bfs() {
    memset(d,0,sizeof(d));
    H=T=0;q[++T]=s;d[s]=1;
    while(H++<T) {
        int x=q[H];
        for(int i=lin[x];i;i=a[i].next) {
            if(a[i].v&&!d[a[i].y]) {
                q[++T]=a[i].y;
                d[a[i].y]=d[x]+1;
                if(a[i].y==t) return 1;
            }
        }
    } 
    return 0;
}

inline int dinic(int x,int flow) {
    if(x==t) return flow;
    int rest=flow,k;
    for(int i=lin[x];i&&rest;i=a[i].next) {
        if(a[i].v&&d[a[i].y]==d[x]+1) {
            k=dinic(a[i].y,min(rest,a[i].v));
            if(!k) d[a[i].y]=0;
            a[i].v-=k;a[i^1].v+=k;
            rest-=k;
        }
    }
    return flow-rest; 
}
int main() {
//    freopen("1.in","r",stdin);
    tot=1;
    read(m); read(n);
    s=1,t=2+n+m;
    for(int i=1;i<=m;i++) {
        read(x);
        add(1,i+1,x);
    }
    for(int i=1;i<=n;i++) {
        read(x);
        for(int j=1;j<=x;j++) {
            read(y);
            if(!st[y]) add(1+y,m+i+1,inf),st[y]=i+m+1;
            else add(st[y],m+i+1,inf);
        }
        read(z);
        add(m+i+1,t,z);
    }
    int flow=0,max_flow=0;
    while(bfs()) {
        while(flow=dinic(s,inf)) max_flow+=flow;
    }
    cout<<max_flow<<endl;
    return 0;
} 
View Code

 [8.28]概率游戏  

好像在模拟赛总结的时候 写过这个题目 所以直接树状数组就行了 或者排序后二分 树状数组开了map 所以比较慢

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
typedef long long ll;
const int N=1000010; 
ll n,X,a[N];
map<ll,ll>c;
inline ll lowbit(ll x) {
    return x&(-x);
}
inline void add(ll x) {
    for(ll i=x;i<=10001000;i+=lowbit(i))
        c[i]+=1;
}
inline ll query(ll x) {
    ll res=0;
    for(ll i=x;i>0;i-=lowbit(i))
        res+=c[i];
    return res;
}
int main() {
    //freopen("1..cpp","r",stdin);
    read(n); read(X);
    for(int i=1;i<=n;i++) {
        read(a[i]); add(a[i]);
    }
    ll sum=0,t=n*(n-1);
    for(int i=1;i<=n;i++) {
        sum+=query(X-a[i]);
        if(a[i]<=X-a[i]) --sum;
    }
    double ans=(double)sum/t;
    printf("%.2lf",ans);
    return 0;
}
View Code

 [6.24]子序列累加和 不是连续子序列啊qwq

一眼望去 我想枚举区间 但是这种区间最值得东西 我们不妨思考一种数据结构 这个题目是要我们求最大值最小值的差值

因为我们都知道答案是$\sum_{i=1}^{n} MAX[i]*a[i]-MIN[i]*a[i]$ 其中MAX[i]表示 以i作为区间最大值 的区间的个数 MIN[i]表示以i作为区间最小值的区间个数

考虑$MAX[i]$这些数组怎么求出来 我们可以类比楼兰图腾那道题目 求出$left[i]表示当前这个元素向左边是多少个区间的最值 right[i]$同理 因为自己也可以产生贡献

所以答案是$left[i]*right[i]+1$ 所以我们考虑用单调栈来维护这个东西 具体怎么做呢

以一个例子为例 求出一个元素左边作为多少个区间的最小值 那么

我们显然是需要维护一个 单调递增的栈 然后我们考虑 当前这个元素比栈顶大 那么就加入

否则 不断弹出栈 那么当前的贡献就是栈顶的贡献+1 然后 这里需要考虑的是 你的while循环里 只能有两个等号 也就是同一个方向的是一个等号

不能全是 也不能全不是 这一点要处理好 为什么呢 我们是为了 避免 漏掉 以及 重复的情况 思考一下

//现在有N个数的数列。现在你定义一个子序列是数列的连续一部分
//子序列的值是这个子序列中最大值和最小值之差
//求所有子序列的值得累加和
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=((x<<1)+(x<<3)+(ch^48));ch=getchar();}
    x*=f;
}
const int N=300010;
LL n;
LL a[N],s1[N],s2[N],s3[N],s4[N],sum1[N],sum2[N],sum3[N],sum4[N];
LL top1=0,ans=0,top2=0,top3=0,top4=0;
int main() {
    //freopen("1.in","r",stdin);
    read(n);
    for(int i=1;i<=n;i++) {
        read(a[i]);
    }  
    s1[++top1]=1; s2[++top2]=n;
    s3[++top3]=1; s4[++top4]=n;
    for(LL i=2;i<=n;i++) {
        for(;a[i]<a[s1[top1]]&&top1;--top1)
            sum1[i]+=sum1[s1[top1]]+1;
        s1[++top1]=i;
    }//正序最小
    for(LL i=n-1;i;i--) {
        for(;a[i]<=a[s2[top2]]&&top2;--top2) 
            sum2[i]+=sum2[s2[top2]]+1; 
        s2[++top2]=i; 
    }//倒序最小
    for(LL i=2;i<=n;++i) {
        for(;a[i]>a[s3[top3]]&&top3;--top3)
            sum3[i]+=sum3[s3[top3]]+1;
        s3[++top3]=i;
    }//正序最大
    for(LL i=n-1;i;--i) {
        for(;a[i]>=a[s4[top4]]&&top4;--top4)
            sum4[i]+=sum4[s4[top4]]+1;
        s4[++top4]=i;
    }//倒序最大
    for(LL i=1;i<=n;++i) 
        ans+=(((sum3[i]+1)*(sum4[i]+1))-((sum1[i]+1)*(sum2[i]+1)))*a[i];
    cout<<ans<<endl;
    return 0;
}
View Code

 [10.21]调整公约数

本来看到 没有思路 但是我们不妨思考一下 每次总是一个数字 除以一个质因数 另一个是 乘上质因数 所以 我们发现 所有数字乘起来

对应的质因数分解 后 质因子对应的指数是不变 所以我们考虑 对于每一个 $a_i$ 我们进行质因数分解 记录一下  每一个数字 对应的质因数 出现的次数

然后 记录一下 这个质因数 一共出现的次数 

接下来 我们要保证次数最少 肯定是考虑 将最小的质因数 的指数 均分 给每一个数字了 然后 我们找到这样的数字 可以保证这样的数字一定是存在的 否则最大公约数就是1

但是需要解决的问题还有一个就是空间问题 我第一遍的做法 是 直接MLE了 所以我考虑 改了一下 当前状态 然后开了一个map 解决了这个事情

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
typedef long long ll;
int vis[1001000];
int x,n;
map<int,map<int,int> >a;
inline ll mul(int a,int b) {
    ll res=0;
    while(b) {
        if(b&1) res=(res+a);
        a=(a+a);
        b>>=1;
    }
    return res;
}
inline ll power(int a,int b) {
    ll res=1;
    while(b) { 
        if(b&1) res=(res*a);
        a=mul(a,a);
        b>>=1;
    }
    return res;
} 
int main() {
    //freopen("1.in","r",stdin);
    read(n);
    int ans=1,sum=0,jud;
    for(int i=1;i<=n;i++) {
        read(x);
        for(int j=2;j<=sqrt(x*1.0);j++) {
            while(x&&x%j==0) {
                x/=j;
                ++vis[j];
                ++a[i][j];
            }
        }
        ++vis[x]; a[i][x]++;
    }
    for(int i=2;i<=1000000;i++) {
        jud=vis[i]/n;
        if(jud) {
            ans=ans*power(i,jud);
            for(int j=1;j<=n;j++)
            {
                if(a[j][i]<jud)
                    sum+=jud-a[j][i];
            }
        }
    } 
    printf("%d %d",ans,sum);
    return 0;
}
View Code

9.8]玻璃球游戏 

这显然是一个 并差集的题目 但是 需要并差集的断开 操作 所以我们不妨 将操作离线 然后 将断开 改成插入 然后 正难则反 好像这个道理只有我刚知道

这里%一下chdy 和 一刀一个小朋友 两个选手 最后 本校oj 需要手动开栈qwq

#include<bits/stdc++.h>
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc(){
  return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:* fs++;
}
inline int read() {
    int This=0,F=1; char ch=getc();
    while(ch<'0'||ch>'9') {if(ch=='-') F=-1;ch=getc();}
    while(ch>='0'&&ch<='9') {This=(This<<1)+(This<<3)+ch-'0';ch=getc();}
    return This*F;
}
void put(int x)
{
    if(x==0)
    {
        putchar('0');
        putchar('\n');
        return;
    }
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    int num=0;char ch[16];
    while(x) ch[++num]=x%10+'0',x/=10;
    while(num) putchar(ch[num--]);
    putchar('\n');
}
const int N=300100;
struct gg {
    int flag,x;
}a[N];
int n,q,ans[N],Next[N],father[N],vis[N];
inline int find(int x) {
    if(x==-1) return -1;
    return father[x]==x?x:father[x]=find(father[x]);
} 
int main() {
//    freopen("1.in.cpp","r",stdin);
    int __size__ = 20 << 20; // 20MB
    char *__p__ = (char*)malloc(__size__) + __size__;
    __asm__("movl %0, %%esp\n" :: "r"(__p__));
    n=read();
    for(register int i=1;i<=n;i++) Next[i]=read(),father[i]=i;
    q=read();
    for(register int i=1;i<=q;i++) {
        a[i].flag=read(); a[i].x=read();
        if(a[i].flag==2) vis[a[i].x]=1;
        ans[i]-=2;
    }
    for(register int i=1;i<=n;i++) {
        if(!vis[i]&&Next[i]) {
            int p=find(Next[i]);
            if(i==p) father[Next[i]]=-1;
            father[i]=Next[i];
        }
    }
    for(register int i=q;i>=1;i--) {
        if(a[i].flag==1) ans[i]=find(a[i].x);
        else {
            int x=a[i].x;
            int y=find(Next[x]);
            if(x==y) father[Next[x]]=-1;
            father[x]=Next[x];
        }
    }
    for(register int i=1;i<=q;i++) {
        if(ans[i]==-1) printf("CIKLUS\n");
        else if(ans[i]!=-2) put(ans[i]);
    }
    return 0;
} 
View Code

数列操作(差分)问题

既然题目已经给提示 是差分了 所以我们可以往差分的思想上思考 然后我们不妨思考一下 

对于这种对原序列的一段区间$l,r$进行+1,-1的操作 加入对于原序列 $a_l,a_r$ 进行+1 我们可以对应到差分序列上 就是$d_l$ 数组 +1 然后 $d_{r+1}$-1 所以对于这种执行区间操作 我

们就转化成了 对差分序列进行两个数字的操作

对于使得最后的数组相同也就是 从差分序列的第2项到第n项 最终变成0的最少操作数 然后我们思考 一下

对于差分序列 我们为了使其变成0 我们可以每次选择一个正数-1 然后选择一个负数+1 然后 我们求出 第二项到第n项的 正数和为 p 负数的绝对值的和为 q

然后 显然操作数就是 $max(p,q)$ 然后 对于 可能的序列数 我们每次讲正数和负数 匹配 然后 我们思考 将其中一个变成0 这样的操作次数是$min(p,q)$

此时序列上剩余 和 为abs(p-q)的数字 然后考虑和 差分数列的$d_1或者d_{n+1}$ 进行配对 然后 我们考虑这样的次数是 abs(p-q)的 然后对于的得到的序列数

我们就是考虑最终 $ d_1 $的所有可能的方案数 就是abs(p-q)+1 因为最后d1可以不变 保持原来的数字

注意开ll 今天我这个同学因为这个爆零好多次qwq

//#include<bits/stdc++.h>
#include<iomanip>
#include<utility>
#include<cctype>
#include<vector>
#include<deque>
#include<map>
#include<stack>
#include<queue>
#include<bitset>
#include<set>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cstring>
#include<string>
#define INF 2147483646
#define up(p,i,n) for(long long i=p;i<=n;i++)
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const long long MAXN=100002;
long long a[MAXN],b[MAXN];
long long n;
long long ans=0,cnt=0;
int main()
{
    n=read();
    up(1,i,n)a[i]=read();
    up(2,i,n)b[i]=a[i]-a[i-1];
    up(2,i,n){if(b[i]<0)ans+=b[i];if(b[i]>0)cnt+=b[i];}
    ans=abs(ans);cnt=abs(cnt);
    put(max(ans,cnt));
    put(abs(ans-cnt)+1);
    return 0;
}
View Code

 [10.17]背包练习 

我又把背包问题忘完了qwq 先复习一下以前的知识8

今天早上做了一个 01背包的第k优解 其实 这个知识点是很早发现的 但一直没写qwq 

我们设$f_{i j k}$表示 前 i 个物品  占了 体积为 j 的背包时 对应第k优解 的最大价值 根据背包的性质 我们显然 优化掉第一维

所以 有用的状态就是 $f_j 和 f_{j-v[i]}$ 但是这个时候 他们不再对应一个个值 而是我们要把他们当成一个序列 开一个数组记录 每次的最优解 直到选够k个

然后考虑 取前面k个 就得到了 第k优解 但是需要注意的是 我们的数组初始化负无穷 然后 $f_{0 1}=0$ 只有这一个是合法状态 

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int N=5010;
const int M=210;
int k,n,m,v[M],w[M],f[N][60],c[60];
int main() {
    read(k); read(m); read(n);
    for(int i=1;i<=n;i++) read(v[i]),read(w[i]);
    memset(f,0xcf,sizeof(f));
    f[0][1]=0;
    for(int i=1;i<=n;i++) {
        for(int j=m;j>=v[i];j--) {
            int l=1,r=1,t=0,tmp=0;
            while(t<k) {
                if(f[j][l]<f[j-v[i]][r]+w[i]) {
                    c[++tmp]=f[j-v[i]][r]+w[i],r++;    
                }
                else c[++tmp]=f[j][l],l++;
                t++;
            }
            for(int s=1;s<=k;s++) f[j][s]=c[s];
        }
    }
    int ans=0;
    for(int i=1;i<=k;i++) ans+=f[m][i];
    printf("%d\n",ans);
    return 0;
} 
View Code

然后我们考虑 这个题目是让我们不选择 第i个时 装满背包的方案数 啊怎么写啊qwq  暴力暴力qwq

qwq 爆零选手 回来写博客了 显然根据我们回归到背包最原来的状态 $f_j$ 的转移只有两个一个是 $f_j 和 f_{j-v[i]}$ 前者表示 不选择第i个物品 后者表示选择第i个物品

然后我们可以 根据这个进行转移 我们先求出 不考虑限制的方案数 然后考虑 不选择第i个物品 就是我们将当前的方案数减去强制选择i的方案数

当进行下一次转移的时候 我们 再将 减去的累加回来 因为一次就不选择一个 复杂度 (nm)  模数写错 我又爆零了

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
int n,m,v[1010],f[10100]; 
int main() {
    //freopen("1.in","r",stdin);
    read(n); read(m);
    for(int i=1;i<=n;i++) read(v[i]);
    f[0]=1;
    for(int i=1;i<=n;i++) {
        for(int j=m;j>=v[i];j--) {
            f[j]+=f[j-v[i]];
            f[j]%=1014;
        }
    }
    for(int k=1,j=v[k];j<=m;j++) {//单独处理第一个 
        f[j]-=f[j-v[k]];
        f[j]%=1014;
    }
    cout<<(f[m]+1014)%1014<<' ';
    for(int k=1;k<=n;) {
        for(int j=m;j>=v[k];j--) {
            f[j]+=f[j-v[k]];
            f[j]%=1014;
        }
        k++;
        for(int j=v[k];j<=m;j++) {
            f[j]-=f[j-v[k]];
            f[j]%=1014;
        }
        cout<<(f[m]+1014)%1014<<' ';
        if(k==n) return 0;    
    }    
}
View Code

最近写了一道贪心 比较水 但是 刚开始T了 后来又写了一遍 总是有这样一堆 板子的 贪心题目 证明我没有咕这个博客

[10.14]小红花 

那么显然看出贪心 就是按照 右端点从小到大排序 每次 从右 向 左 放 没有就放 有就跳过 其实不是说这个题有多巧妙 模型就是 若干个区间 限制每个区间最少选择多少个点 然后考虑选择最少的点是多少

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int N=5100;
struct gg {
    int l,r,x;
}a[N];
inline bool mycmp(gg x,gg y) {
    return x.r==y.r?x.l<y.l:x.r<y.r;
}
int n,m;
bool p[310000];
int main() {
    //freopen("1.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) {
        scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].x);
    }
    sort(a+1,a+m+1,mycmp);
    int ans=0;
    for(int i=1;i<=m;i++) {
        int v=a[i].x,r=a[i].r;
        for(int j=a[i].l;j<=a[i].r;j++) {
            if(p[j]) v--;
        }
        for(;v>0;r--) {
            if(!p[r]) {
                p[r]=1;
                v--;
                ans++;
            }
        }
    }
    cout<<ans<<endl;
} 
View Code

是最近我发现好多贪心 都是这样

比如 雷达监测 我们转化模型之后 发现就是若干个 区间 然后你选择 最少的点 st 所有的区间都至少有一个点 被选中

将所有区间按右端点从小到大排序 依次考虑每个区间

如果当前区间包含最后一个选择的点,则直接跳过

如果当前区间不包含最后一个选择的点 则在当前区间的右端点的位置选一个新的点 选择 右端点 是最优 不会更差的做法 仔细理解这个贪心的过程

#include<bits/stdc++.h>
using namespace std;
typedef pair<double,double> PDD;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
const double eps=1e-9;
const int inf=1e10;
const int N=1010;
PDD g[N];
int n,d,x,y;
int main() {
    //freopen("1.in","r",stdin);
    read(n); read(d);
    int flag=0;
    for(int i=1;i<=n;i++) {
        read(x); read(y);
        if(y>d) {
            flag=1;
            break;
        }
        double len=sqrt(d*d-y*y);
        g[i]={x+len,x-len};
    }
    if(flag) {
        puts("-1");
        return 0;
    }
    else {
        sort(g+1,g+n+1);
        int res=0;
        double last=-inf;
        for(int i=1;i<=n;i++) {
            if(g[i].second>last+eps) {
                res++;
                last=g[i].first;
            }
        }
        cout<<res<<endl;
        return 0;
    }
}
View Code

还比如 蓄栏预定 每个牛有个吃草时间区间 然后 区间有重叠的 不能在一个 蓄栏 考虑最少准备多少个 蓄栏

那么这个就是 按照左端点从小到大排序 然后 开一个小根堆 维护一个右端点 每次判断 右端点是否位于下一个区间里 如果是 那么 考虑此时累加 个数 并且放进堆里 维护此时 把位置更新

如果不是 那么安排放在一起  都是acwing上的贪心题目

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
const int N=50010;
struct gg  {
    int l,r,id;
}a[N]; 
struct pink {
    int r,size;
    /*int operator < (pink &a) const{
        return a.r<r;
    }*/ 
    int friend operator <(pink a,pink b)
    {
        return a.r>b.r;
    }
};
inline bool mycmp(gg x,gg y) {
    return x.l==y.l?x.r<y.r:x.l<y.l;
}
priority_queue<pink>q;
int n,pos[N];
int main() 
{
    //freopen("1.in","r",stdin);
    read(n);
    for(int i=1;i<=n;i++) {
        read(a[i].l); read(a[i].r);
        a[i].id=i;
    }
    sort(a+1,a+n+1,mycmp);
    for(int i=1;i<=n;i++) {
        if(q.empty()||q.top().r>=a[i].l) {
            pink h;
            h.r=a[i].r;h.size=q.size();
            q.push(h);
            pos[a[i].id]=h.size;
        }
        else {
            pink h=q.top(); q.pop();
            pos[a[i].id]=h.size;
            h.r=a[i].r;
            q.push(h);
        }
    }
    cout<<q.size()<<endl;
    for(int i=1;i<=n;i++) cout<<pos[i]+1<<endl;
    return 0;
}
View Code

[8.21]8的倍数

这个题 看到 呜呜呜 只会写暴力 考虑怎么优化这个题目呢 思考了一会 发现没有什么思路

那么考虑 此时 我去问了一下强者 强者跟我说 容斥一下就行了 不会 出去和老师恰了个饭 一餐的方便面还不错 回到机房 等待晚上讲 前四场的模拟赛题解 那么思考了一下 发现了 其中的规律

其实 我们可以预处理 1-y 的满足条件的 个数 和1 -  x-1 的个数 然后二者相减 就是个数 

考虑数论了 显然是2^n 枚举因子的使用情况 那么寻找当前使用因子和8的lcm 根据容斥 那个神奇的例子 我们奇减偶加 然后用x-1或者y除以这个lcm 然后就求出了出现个数

后来发现可以dfs 其实二进制枚举就可以

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
typedef long long ll;
const int N=20;
int n,m,x,y,a[N];
inline ll gcd(ll a,ll b) {
    return b?gcd(b,a%b):a;
}
inline ll ask()  {
    ll ans=0;
    for(int i=0;i<(1<<n);i++) {
        ll tot=0,num=8;
        for(int j=0;j<n;j++) {
            if(i&(1<<j)) {
                num*=a[j]/gcd(a[j],num);
                if(num>y) break;
                tot++;
            } 
        }
        if(tot&1) ans-=y/num;
        else ans+=y/num;  
    }
    return ans;
} 
inline ll ask1() {
    ll ans=0;
    for(int i=0;i<(1<<n);i++) {
        ll tot=0,num=8;
        for(int j=0;j<n;j++) {
            if(i&(1<<j)) {
                num*=a[j]/gcd(a[j],num);
                if(num>(x-1)) break;
                tot++;
            }
        }
        if(tot&1) ans-=(x-1)/num;
        else ans+=(x-1)/num;
    }
    return ans;
}
int main() {
    read(n);
    for(int i=0;i<n;i++) read(a[i]);
    read(x); read(y);
    printf("%lld\n",ask()-ask1());
    return 0;
} 
View Code

免费馅饼

长了个dp的样子 确实这个dp 正推 倒推都可以 这里我写了倒推 我们设状态$dp[i][j]$表示在第 i 个时刻 位于 j 地点的最大分数

那么我们这个时候要求的终点状态就是dp[0][w/2+1] 因为最开始位于舞台中央

所以 $dp[i][j]=dp[i+1][j+k]$ k 表示移动方向

那么我们倒序循环 时刻 以及 舞台位置 每个方向取最大 有一点 能不能不动 所以优先判断不动

对于方案输出 我们只需要再次模拟一遍 寻找答案的过程 然后记录一下 方向输出即可 

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
const int N=1100;
int n,m,w,h,now;
struct gg {
    int tim,pos,speed,val;
}a[N];
int f[N][N];//f[i][j]表示当前是i时刻位于j地点捡到的最大分数 
inline int slove(int tim,int pos) {
    int ans=0;
    if(f[tim+1][pos]>ans) {
        ans=f[tim+1][pos];
        now=0;
    }
    if(f[tim+1][pos-2]>ans&&pos-2>0) {
        ans=f[tim+1][pos-2];
        now=-2;
    }
    if(f[tim+1][pos-1]>ans&&pos-1>0) {
        ans=f[tim+1][pos-1];
        now=-1;
    }
    if(f[tim+1][pos+1]>ans&&pos+1<=w) {
        ans=f[tim+1][pos+1];
        now=1;
    }
    if(f[tim+1][pos+2]>ans&&pos+1<=w) {
        ans=f[tim+1][pos+2];
        now=2;
    }
    return ans;
}
int main() {
    //freopen("1.in","r",stdin);
    read(w); read(h); h--;
    int n=1;
    while(scanf("%d%d%d%d",&a[n].tim,&a[n].pos,&a[n].speed,&a[n].val)==4) {
        if((h%a[n].speed==0)||a[n].tim==0) {
            a[n].tim+=(h/a[n].speed);
            m=max(m,a[n].tim);
            n++;
        }
    }--n;
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++) {
        f[a[i].tim][a[i].pos]+=a[i].val;
    } 
    for(int i=m-1;i>=0;i--) {
        for(int j=1;j<=w;++j) {
            f[i][j]+=slove(i,j);
        }
    } 
    cout<<f[0][w/2+1]<<endl;
    for(int i=0,j=w/2+1;;++i) {
        if(!slove(i,j)) break;
        j+=now;
        cout<<now<<endl;    
    }
    return 0;
} 
View Code

 [8.20]校门外的树(加强版)

我也不知道 为什么我要 放出来 反正我觉得是个线段树 区间修改 区间查询 很好写  或许我只是想放个代码

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
const int N=100100;
int n,m,x,y;
struct gg {
    int l,r,tag,sum;
    #define tag(p) t[p].tag
    #define l(p) t[p].l
    #define r(p) t[p].r
    #define sum(p) t[p].sum 
}t[N*4];
inline void build(int p,int l,int r) {
    if(l==r) {
        sum(p)=1;
        return ; 
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    sum(p)=sum(p<<1)+sum(p<<1|1);
}
inline void pushdown(int p) {
    if(tag(p)) {
        tag(p<<1)=1;
        tag(p<<1|1)=1;
        sum(p<<1)=0;
        sum(p<<1|1)=0;
        tag(p)=0;
    }
}
inline void change(int p,int l,int r,int ll,int rr) {
    if(ll<=l&&rr>=r) {
        tag(p)=1;
        sum(p)=0;
        return ;
    }
    pushdown(p);
    int mid=(l+r)>>1;
    if(ll<=mid) change(p<<1,l,mid,ll,rr);
    if(rr>mid) change(p<<1|1,mid+1,r,ll,rr); 
    sum(p)=sum(p<<1)+sum(p<<1|1); 
}
int main() {
    //freopen("1.in","r",stdin);
    read(m); read(n);
    build(1,1,n);
    for(int i=1;i<=m;i++) {
        read(x); read(y);
        change(1,1,n,x,y);
        printf("%d\n",t[1].sum);
    }
    return 0;
} 
View Code

 

 

posted @ 2019-09-26 15:26  Tyouchie  阅读(327)  评论(0编辑  收藏  举报