10.24模拟赛题解
这次考了ZR普专提的比赛 我原来是见过这几个题目 但是没做 就碰到了 反正前两道 是个送分吧
然后 后两道 的dp 有一点难度 然后容斥 也有一点难度
首先 大家都有很多做法 二分 模拟都很多 不过也存在(n) 的做法 这里看到数据只有1000 所以果断写了n^2 模拟赛 我们记录每个 数字出现的次数
此时 我们肯定是删除一个序列 st 序列 剩下的数字 都出现一次 并且 这个二分的单调性 也是很显然的 删除长度为x可以 当然删除x+1 也可以
所以 我们存在每个数字 出现的此时 然后 我们枚举 删除的起点是什么 每次取min即可 只有所以的数字删除到只剩2个 此时删除这个数字 并且更新 此时是否删除完了
那么 我们就可以得到答案
//删除x段可以 那么删除x+1段绝对也可以 没什么用处好像 贪心似乎是正解 //n只有1000 但是ai是1e9 考虑离散化? #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=1100; int flag,cnt,cntt,n,vis[N],num[N],numm[N]; ll a[N],b[N]; int main() { // freopen("1.in","r",stdin); freopen("sequence.in","r",stdin); freopen("sequence.out","w",stdout); read(n); for(int i=1;i<=n;i++) { read(a[i]); b[i]=a[i]; } sort(b+1,b+n+1); int tot=unique(b+1,b+n+1)-b-1; int flag=0; for(int i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+tot+1,a[i])-b; num[a[i]]++; if(num[a[i]]>=2) { flag=1; if(!vis[a[i]]) { vis[a[i]]=1; cnt++;//一共有cnt个数字 这些数字的个数是大于等于2的 } } } // cout<<cnt<<endl; if(!flag) {puts("0");return 0;} int ans=1<<30; for(int i=1;i<=n;i++) {//枚举左端点 for(int j=1;j<=n;j++) numm[j]=num[j];//记录j出现的次数 cntt=cnt; for(int j=i;j<=n;j++) { if(numm[a[j]]==2) {//只有到这个时候 才能把前面和他一样的删掉 cntt--; if(!cntt) {//还要保证删完了 所有的 出现次数大于等于2的数字 ans=min(ans,j-i+1); break; } } --numm[a[j]]; } } printf("%d\n",ans); return 0; }
直接暴力 求周期 单独处理一小段 因为 个位数 每次都是当前这个数的倍数 只需要求有多少个10的周期即可 处理出来一个周期1-9倍的数字的个位数 然后乘上周期 然后单独处理
#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; } ll T,x,y; int main() { // freopen("1.in","r",stdin); freopen("number.in","r",stdin); freopen("number.out","w",stdout); read(T); while(T--) { read(x); read(y); ll zk=x/y,t=zk/10,res=zk-t*10; ll ans=0; for(int i=1;i<=9;i++) { ans=(ans+(i*y)%10); } ans*=t; for(int i=1;i<=res;i++) { ans=(ans+(i*y)%10); } printf("%lld\n",ans); } return 0; }
反正看一下就是一个dp 不过 当时学弟写了一个区间dp 因为他发现 能被利用 一定是包含包含的关系 其实这个是正确的 但是 我们为了 不变成这个高的复杂度
我们只需要 按照一定的顺序排序 即可 考虑 此时这个顺序 其实很容易 跌坑吧qwq 因为一个物品要想被取出来 肯定是 此时 他是序列最上面
所以 我们按照取出物品 从小到大排序 如果相同 按照放入时间从小到大排序 此时 不一定是一直合法 不过 如果你是这样
先把所有物品按照拿走的时间从小到大排序,拿走的时间相同就按照放上去的时间从大到小。那么一件物品上方的物品就一定会在它的前面。 如果他们都被取出来的话)
考虑 dp 设$f[i][j]$表示 i 以及 i 上面物品在所有时刻中最大重量为 j 时的最大收益。
转移的时候,我们需要枚举所有 i 上面的物品,维护一个 $g[i]$表示时刻 i 之前物品的最大收益是多少。然后直接转移就好了)这是题解的话 这个转移 真的很好写吗qwq自闭嘤嘤嘤
#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=1100; ll f[510][2100],w[N];//f[i][j]表示前i个物品 最大承重是j时的最大价值 //w[i]表示在i这个时刻之前 所能获得的最大价值 struct gg { int in,out,w,s; ll v; }a[510]; int n,s; inline bool mycmp(gg x,gg y) {return x.out==y.out?x.in>y.in:x.out<y.out;} inline void dp() { for(int i=1;i<=n;i++) { for(int j=a[i].w;j<=s;j++) { int now=a[i].in;w[now]=0; for(int k=1;k<i;k++) { if(a[k].in>=a[i].in) { while(now<a[k].out) { now++; w[now]=w[now-1]; } w[now]=max(w[now],w[a[k].in]+f[k][min(a[i].s,j-a[i].w)]); } } f[i][j]=w[now]+a[i].v; } } } int main() { // freopen("1.in","r",stdin); freopen("knapsack.in","r",stdin); freopen("knapsack.out","w",stdout); read(n); read(s); for(int i=1;i<=n;i++) { read(a[i].in); read(a[i].out); read(a[i].w); read(a[i].s); read(a[i].v); } sort(a+1,a+n+1,mycmp); a[++n].out=a[n].out,a[n].in=0,a[n].s=s;//增加一个物品 st 能到达终点 dp(); // for(int i=0;i<=n;i++) cout<<w[i]<<' '; // puts(""); printf("%lld\n",f[n][s]); return 0; }
给你若干个二进制数字 然后 让你从中选择若干个 st 能够or起来全部是1
首先m是20 一个显然的状压就出来 此时 我们考虑 怎么做这个事情 一个很明显的思路是 容斥 我们怎么容斥呢 对于这种计数问题 我们不妨考虑容斥
其实容斥 是很容易想到的 但是不会写容斥 怎么办呢qwq 啊啊啊啊啊我去写几道容斥的题目再说
对于这个题目 我们预处理出来 构成一个状态的子集的箱子数量 此时转移就是$\sum (-1)^{m-|s|}2^{sum[s]}$ 此时我来yy一波
这是一个 高维前缀和的东西 确实是一个高维前缀和的东西 此时 我们考虑 容斥是存在递推式子的 显然这个我也没写qwq
后来 我询问了学长 为什么 容斥式子是这样的 学长说 此时(-1) 的系数 为什么 是0的个数 是因为 此题需要钦定0的个数 从而求解
因为 2^sum[s] 表示的是只有这几个是可以备选的集合 然后选择或不选 然后此时减去一个零的 加上两个零的 依次是这样 作高维前缀和即可
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; 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; } ll n,m,x,k,p[1001000]; ll cnt[1<<21],sum[1<<21]; /*inline ll ksm(int a,ll b) { ll res=1; while(b) { if(b&1) res=(res*a)%mod; a=(a*a)%mod; b>>=1; } return res; }*/ int main() { // freopen("1.in","r",stdin); freopen("toy.in","r",stdin); freopen("toy.out","w",stdout); read(n); read(m); p[0]=1; for(int i=1;i<=n;i++) p[i]=p[i-1]*2%mod; for(int i=1;i<=n;i++) { read(k);int c=0; for(int j=1;j<=k;j++) { read(x); c|=(1<<(x-1)); } sum[c]++;//记录这个状态的箱子个数 } for(int i=1;i<=m;i++) { for(int s=0;s<(1<<m);s++) { if(s&(1<<(i-1))) { sum[s]+=sum[s^(1<<(i-1))]; cnt[s]++;//二进制下几个1 } } } // for(int i=1;i<(1<<m);i++) cout<<i<<' '<<cnt[i]<<endl; // puts(""); ll ans=0; for(int i=0;i<(1<<m);i++) {//容斥 if((m-cnt[i])&1) { // ans=((ans-ksm(2,sum[i]))%mod+mod)%mod; ans=((ans-p[sum[i]])%mod+mod)%mod;//qmod??? } else { // ans=((ans+ksm(2,sum[i]))%mod+mod)%mod; ans=((ans+p[sum[i]])%mod+mod)%mod; } } printf("%lld\n",ans%mod); return 0; }