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