校oj不少题目仍是很好的 考虑给他们蟹蟹题解Yios
更新于:10.24c++
打扫卫生 题目大意:n个房间 m个同窗 每一个房间至少两个同窗 求方案数git
显然是个计数类的题目 技术类题目 真的 须要有必定数学感受8... 头有点大奥 数组
而后咱们考虑这道题目显然是一个组合数的问题 而后须要用到隔板法 因此在这里 我浅析一下隔板法的应用 有多学习了一个好东西网络
具体隔板法 对于n个有顺序的球 咱们考虑在中间放入m个板 而后规定咱们不能在两个球之间放两个及以上个隔板 而后两段不能放隔板 问你方案数数据结构
咱们不妨考虑 在这n-1个空位置 放入m个球 那么方案数 显然是$\binom{n-1}{m}$ide
听说今年高联数学二试也考了个隔板法?? 仍是比较好写的那个题目 究竟是形如什么样的式子 能够 使用隔板法求解问题呢学习
咱们不妨举几个例子 优化
求解$\sum_{i=1}^{m} x_i=n$ 有多少个正整数解 ui
对于这种状况 咱们能够考虑是在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; }
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; }
好像在模拟赛总结的时候 写过这个题目 因此直接树状数组就好了 或者排序后二分 树状数组开了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; }
[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; }
原本看到 没有思路 可是咱们不妨思考一下 每次老是一个数字 除以一个质因数 另外一个是 乘上质因数 因此 咱们发现 全部数字乘起来
对应的质因数分解 后 质因子对应的指数是不变 因此咱们考虑 对于每个 $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; }
这显然是一个 并差集的题目 可是 须要并差集的断开 操做 因此咱们不妨 将操做离线 而后 将断开 改为插入 而后 正难则反 好像这个道理只有我刚知道
这里%一下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; }
既然题目已经给提示 是差分了 因此咱们能够往差分的思想上思考 而后咱们不妨思考一下
对于这种对原序列的一段区间$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; }
我又把背包问题忘完了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; }
而后咱们考虑 这个题目是让咱们不选择 第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; } }
最近写了一道贪心 比较水 可是 刚开始T了 后来又写了一遍 老是有这样一堆 板子的 贪心题目 证实我没有咕这个博客
那么显然看出贪心 就是按照 右端点从小到大排序 每次 从右 向 左 放 没有就放 有就跳过 其实不是说这个题有多巧妙 模型就是 若干个区间 限制每一个区间最少选择多少个点 而后考虑选择最少的点是多少
#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; }
是最近我发现好多贪心 都是这样
好比 雷达监测 咱们转化模型以后 发现就是若干个 区间 而后你选择 最少的点 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; } }
还好比 蓄栏预约 每一个牛有个吃草时间区间 而后 区间有重叠的 不能在一个 蓄栏 考虑最少准备多少个 蓄栏
那么这个就是 按照左端点从小到大排序 而后 开一个小根堆 维护一个右端点 每次判断 右端点是否位于下一个区间里 若是是 那么 考虑此时累加 个数 而且放进堆里 维护此时 把位置更新
若是不是 那么安排放在一块儿 都是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; }
这个题 看到 呜呜呜 只会写暴力 考虑怎么优化这个题目呢 思考了一会 发现没有什么思路
那么考虑 此时 我去问了一下强者 强者跟我说 容斥一下就好了 不会 出去和老师恰了个饭 一餐的方便面还不错 回到机房 等待晚上讲 前四场的模拟赛题解 那么思考了一下 发现了 其中的规律
其实 咱们能够预处理 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; }
长了个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; }
我也不知道 为何我要 放出来 反正我以为是个线段树 区间修改 区间查询 很好写 或许我只是想放个代码
#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; }