7.23 学习笔记
模拟赛。前一天又没有睡好。
T1,T2大水题,但是 T1 竟然忘了输出导致 \(100\to 0\) 。
郁闷。。。emmm
T1
贪心放就可以。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100100
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int a[N],n,k,cnt;
int main(){
read(n);read(k);for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1);
cnt++;ll r=a[1]+k;
for(int i=2;i<=n;i++){
if(a[i]<=r) continue;
cnt++;r=a[i]+k;
}
printf("%d",cnt);
return 0;
}
T2
分类讨论往里面灌水就可以。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> T Min(T a,T b){
return a<b?a:b;
}
int n,s,a[N],b[N],minn=INF,ans;
inline int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(s);
for(int i=1;i<=n;i++){
read(a[i]);b[a[i]+1]++;
minn=Min(ans,a[i]);
}
for(int i=1;i<=1000;i++) b[i]+=b[i-1];
ans=minn;
for(int i=minn+1;i<=1000;i++){
if(s<b[i]) break;
s-=b[i];ans=i;
}
// while(s>=n){
// s-=n;ans++;
// }
// printf("s:%d\n",s);
if(s>=n){
ans+=s/n;
s=s%n;
}
int now;
if(ans>=1000) now=n;
else now=b[ans+1];
if(s==0){
printf("%d\n",ans);return 0;
}
else{
int d=gcd(s,now);
if(ans!=0) printf("%d+%d/%d\n",ans,s/d,now/d);
else printf("%d/%d\n",s/d,now/d);
}
return 0;
}
T3
首先修改不是问题,我们可以维护两个变量 \(a\),和 \(b\) 来看两个数组的变化是多少。
两个 \(\log\) 怎么做?考虑到中位数具有可二分性,我们在第一个数组里面二分,然后再第二个数组里找小于它的有多少个数,小于等于它的有多少个数,就可以得到这是否是一个中位数,找大来还是找小了,然后进行相应的调整即可。两个 \(\log\) 据说可以卡过去。
一个 \(\log\) 怎么做?我们首先在第一个数组里面二分中位数,那么其实第二个数组里令这个数是中位数的位置是固定的,比如说我二分出来的一个位置是 \(mid\) ,有 \(mid-1\) 个数比其小,然后我们要求一共要有 \(len\) 个数比其小,这个时候我们就需要看第二个数组的对应位置是否能放在这个数的左边,如果能,我们尝试去找更大的中位数,如果不能,那么这个数一定不合法。
当然我说的只是一个概括性的,实际上这个题还有很多细节,包括中位数除以 \(2\) 的处理等等。需要讨论的情况非常多,如果不希望一遍遍调试的话,建议还是在纸上写一下。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;
const ll INF=1e16;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Min(T a,T b){
return a<b?a:b;
}
ll A,B,a[N],b[N],n,m,q,len,other;
bool op;
inline bool Check(ll *a,int m,ll A,ll *b,int n,ll B,int mid){
op=0;
int now=a[mid]+A;other=len-mid+1;
if(other>n) return 0;
if(other<0) return 1;
if(other==0){
if(b[other+1]+B>=now) op=1;
return 1;
}
int bnow=b[other]+B;
if(other==n||b[other+1]+B>=now) op=1;
if(bnow<=now) return 1;
else return 0;
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(m);read(n);read(q);
for(int i=1;i<=m;i++) read(a[i]);
for(int i=1;i<=n;i++) read(b[i]);
a[m+1]=INF;b[n+1]=INF;
len=(n+m)>>1;if(((n+m)&1)==0) len--;
for(int i=1;i<=q;i++){
A=B=0;
int id,val;read(id);read(val);
if(id==1) A+=val;else B+=val;
int l=1,r=m;
while(l<r){
int mid=(l+r)>>1;
if(Check(a,m,A,b,n,B,mid)) r=mid;
else l=mid+1;
}bool pd=Check(a,m,A,b,n,B,l);
// printf("l1:%lld\n",l);
if(op&&pd){
if((n+m)&1) printf("%lld\n",a[l]+A);
else{
printf("%lld",(a[l]+A+Min(a[l+1]+A,b[other+1]+B))/2);
if((a[l]+A+Min(a[l+1]+A,b[other+1]+B))%2) puts(".5");
else puts("");
}
continue;
}
l=1,r=n;
while(l<r){
int mid=(l+r)>>1;
if(Check(b,n,B,a,m,A,mid)) r=mid;
else l=mid+1;
}pd=Check(b,n,B,a,m,A,l);
// printf("l2:%lld\n",l);
if(op&&pd){
if((n+m)&1) printf("%lld\n",b[l]+B);
else{
printf("%lld",(b[l]+B+Min(b[l+1]+B,a[other+1]+A))/2);
if((b[l]+B+Min(b[l+1]+B,a[other+1]+A))%2) puts(".5");
else puts("");
}
continue;
}
}
}
T4
一道非常好的题目,是一道在 AC 自动机上状压加数位 dp。其实 AC 自动机上 dp 比较老套,结合状压和数位 dp 的特点,我们设:
表示目前 dp 到第 \(i\) 位,自动机上状态为 \(j\) ,状压为 \(k\) ,压的是包含字符串的集合,第一个 \(0/1\) 表示是否顶上界,第二个 \(0/1\) 表示是否为前导 \(0\) 。
我们考虑这个东西怎么转移,首先 AC 自动机上的转移我们不用做太多考虑,这是因为我们其实是在 Trie 图上进行 dp,而 Trie 图保证了任何一个状态的的任何一个转移都有意义。
我们考虑下一位填什么,假设填 \(c\) ,那么我们发现我们似乎少了一个东西,那就是组成 \(f_{i,j,k,0/1,0/1}\) 这个状态的数的方案数是多少,有多少个方案,就要加多少个 \(c\) ,相当于在原来每一个数的基础上往后面加一个 \(c\) 。
于是我们设 \(g_{i,j,k,0/1,0/1}\) 表示与 \(f\) 对应的方案数是多少,然后就可以转移了。
要小心前导 \(0\) ,我们优先给非前导 \(0\) 的转移,前导 \(0\) 我们强制把他们弄在 \(0\) 这个状态,实际上是每次枚举完位数 \(i\) 后进行一次预处理。这样做带来的另一个好处就是最后一维不用记了。但是在程序里我为了好理解记了一个。
不要忘记在构建 fail 数组的时候把后缀的 end 继承下来。
据党星宇说小的维数放前面会更快,空间越小越好。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
// #define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 110
#define M 40
using namespace std;
// #define DEBUG
// #define DEBUG2
const int INF=0x3f3f3f3f;
const int mod=998244353;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
ll f[N][N][M][2],g[N][N][M][2],n,w[N],len;
//f 记得是综合,g是方案数,之所以要记方案数是不知道里面究竟有多少个数,以便加上多少个位。
char s[N],l[N],r[N];
struct AC_automata{
int ch[N][10],end[N],fail[N],tot;
inline AC_automata(){tot=0;}
inline void Insert(char *s,int id){
int len=strlen(s),p=0;
for(int i=0;i<=len-1;i++){
int c=s[i]-'0';
if(!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c];
}
end[p]|=(1<<(id-1));
}
inline void BuildFail(){
queue<int> q;while(q.size()) q.pop();
for(int i=0;i<=9;i++) if(ch[0][i]) q.push(ch[0][i]);
while(q.size()){
int top=q.front();q.pop();
for(int i=0;i<=9;i++){
if(ch[top][i]){
fail[ch[top][i]]=ch[fail[top]][i],q.push(ch[top][i]);
end[ch[top][i]]|=end[fail[ch[top][i]]];
}
else ch[top][i]=ch[fail[top]][i];
}
}
}
inline void Work(char *s,bool op){
len=strlen(s);
for(int i=0;i<=len-1;i++) w[i+1]=s[i]-'0';
if(op){
reverse(w+1,w+len+1);
w[1]--;int now=1;
while(w[now]<0){w[now]+=10;w[now+1]--;now++;}
if(!w[len]) len--;
reverse(w+1,w+len+1);
}
}
inline ll Solve(char *s,bool op){
memset(f,0,sizeof(f));memset(g,0,sizeof(g));
Work(s,op);ll ans=0;
for(int i=1;i<=w[1];i++){
(f[1][ch[0][i]][0|end[ch[0][i]]][(i==w[1])]+=i)%=mod;
(g[1][ch[0][i]][0|end[ch[0][i]]][(i==w[1])]+=1)%=mod;
}
for(int i=1;i<=len-1;i++){
for(int k=0;k<=(1<<n)-1;k++){
for(int j=0;j<=tot;j++){
for(int limits=0;limits<=1;limits++){
//优先处理状态 f[i][j][k][limits][0]
if(f[i][j][k][limits]){
int up=limits?w[i+1]:9;
//枚举要填的数字。
for(int c=0;c<=up;c++){
#ifdef DEBUG2
printf("%d %d %d %d 0 to %d %d %d %d 0\n",i,j,k,limits,i+1,ch[j][c],k|end[ch[j][c]],limits&&c==w[i+1]);
#endif
(f[i+1][ch[j][c]][k|end[ch[j][c]]][(limits&&c==w[i+1])]+=f[i][j][k][limits]*10%mod+g[i][j][k][limits]*c%mod)%=mod;
(g[i+1][ch[j][c]][k|end[ch[j][c]]][(limits&&c==w[i+1])]+=g[i][j][k][limits])%=mod;
}
}
}
}
}
//然后处理状态 f[i][j][k][limits][1]
//我们直接枚举这一位填啥,我们强制前导0一直在状态0
for(int c=1;c<=9;c++){
#ifdef DEBUG2
printf("spe:%d 0 0 0 1 to %d %d %d 0 0\n",i,i+1,ch[0][c],0|end[ch[0][c]]);
#endif
(f[i+1][ch[0][c]][0|end[ch[0][c]]][0]+=c)%=mod;
(g[i+1][ch[0][c]][0|end[ch[0][c]]][0]+=1)%=mod;
}
}
#ifdef DEBUG2
puts("");
for(int i=1;i<=len;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<=(1<<n)-1;k++){
for(int limits=0;limits<=1;limits++){
printf("i:%d j:%d k:%d limits:%d pre:0 f:%lld\n",i,j,k,limits,f[i][j][k][limits]);
printf("i:%d j:%d k:%d limits:%d pre:0 g:%lld\n",i,j,k,limits,g[i][j][k][limits]);
}
}
}
puts("");
}
#endif
for(int j=0;j<=tot;j++){
ans=(ans+f[len][j][(1<<n)-1][0])%mod;
ans=(ans+f[len][j][(1<<n)-1][1])%mod;
}
return ans;
}
};
AC_automata ac;
signed main(){
#ifdef DEBUG
freopen("my.in","r",stdin);
freopen("my.out","w",stdout);
#endif
read(n);scanf("%s%s",l,r);
for(int i=1;i<=n;i++){
scanf("%s",s);ac.Insert(s,i);
}ac.BuildFail();
printf("%lld\n",((ac.Solve(r,0)-ac.Solve(l,1))%mod+mod)%mod);
return 0;
}
其余题目
T1 AT2165 [AGC006D] Median Pyramid Hard
中位数有二分性,我们考虑二分,设当前值为 \(mid\) 我们把小于等于 \(mid\) 的数弄成 \(0\),把大于 \(mid\) 的数弄成 \(1\),现在就是来考察最上面的数是 \(0\) 还是 \(1\)。
我们发现一下规律:
- \(n-1,n,n+1\) 这三个位置的数至关重要,如果有两个相邻的数相等,那么这两个数上面的所有数都相等,进而最上面那个数也就和这两个相邻的数相等。
- 否则,这三个数就会像是 \(101\) 或 \(010\) 一样,我们看这两边哪个相邻相等的数里他们最近,最近的就是就上面的数,这个通过手玩几个数据不难验证,再否则,那么就是整个最下一层都没有相邻相等的数,我们这个时候看一下层数是多少,这个因为最中间这一列一定是 \(010101\) 变化的。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;
// #define FRE
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,a[N],b[N];
inline bool Check(int mid){
for(int i=1;i<=2*n-1;i++) if(a[i]<=mid) b[i]=0;else b[i]=1;
if(b[n-1]==b[n]||b[n]==b[n+1]) return !b[n];
int l=n-1,r=n+1;
while(l>=2&&r<=2*n-2){
if(b[l]==b[l-1]) break;
else if(b[r]==b[r+1]) break;
l--;r++;
}
if(l!=1&&r!=2*n-1){
if(b[l]==b[l-1]) return !b[l];
else if(b[r]==a[r+1]) return !b[r];
}
else return b[n]^(n&1);
}
int main(){
#ifdef FRE
freopen("my.in","r",stdin);
freopen("my.out","w",stdout);
#endif
read(n);for(int i=1;i<=2*n-1;i++) read(a[i]);
int l=1,r=2*n-1,ans;
while(l<=r){
int mid=(l+r)>>1;
if(Check(mid)){r=mid-1;ans=mid;}
else l=mid+1;
}
printf("%d",ans);
return 0;
}
T2 EOJ 3618 中位数
第一次接触的这个 OJ,这个 OJ 的设计非常不错。
我们仍然用上面的思路,因为要让中位数最大,所以我们令小于 \(mid\) 的设置为 \(0\),大于等于 \(mid\) 的设置为 \(1\)。
我们发现这样做不了,所以我们尝试用让小于 \(mid\) 的值设置为 \(-1\)。我们发现现在可以 dp 了,我们设 \(f_k\) 为从 \(k\) 到 \(n\) 的路径中点权和最大是多少,如果这个和大于等于 \(0\) 那么证明大于等于的至少和小于的相等,满足条件,所以我们 dp 判断 \(f\) 数组即可。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1000010
#define M number
using namespace std;
// #define FRE
// #define debug
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}
struct edge{
int to,next;
inline void Init(int to_,int ne_){to=to_;next=ne_;}
};edge li[N];int head[N],tail;
inline void Add(int from,int to){li[++tail].Init(to,head[from]);head[from]=tail;}
int a[N],b[N],n,m,c[N],f[N],rd[N],TopSeq[N],Toptail;
queue<int> q;
inline void TopSort(){
for(int i=1;i<=n;i++) if(!rd[i]) q.push(i);
while(q.size()){
int top=q.front();q.pop();TopSeq[++Toptail]=top;
for(int x=head[top];x;x=li[x].next){
int to=li[x].to;rd[to]--;if(!rd[to]) q.push(to);
}
}
#ifdef debug
for(int i=1;i<=n;i++) printf("%d ",TopSeq[i]);puts("");
#endif
}
bool op=1;
inline bool Check(int mid){
memset(f,-INF,sizeof(f));
for(int i=1;i<=n;i++) if(a[i]<mid) c[i]=-1;else c[i]=1;f[n]=c[n];
for(int i=n;i>=1;i--){
int now=TopSeq[i];
for(int x=head[now];x;x=li[x].next){
int to=li[x].to;if(f[to]!=-INF) f[now]=Max(f[now],f[to]+c[now]);
}
}
if(f[1]==-INF) op=0;
return f[1]>=0;
}
int main(){
#ifdef FRE
freopen("my.in","r",stdin);
freopen("my.out","w",stdout);
#endif
read(n);read(m);for(int i=1;i<=n;i++){read(a[i]);b[i]=a[i];}
for(int i=1;i<=m;i++){int from,to;read(from);read(to);Add(from,to);rd[to]++;}
TopSort();sort(b+1,b+n+1);int l=1,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;if(Check(a[mid])){l=mid+1;ans=mid;}else r=mid-1;
if(!op){printf("-1\n");return 0;}
}
#ifdef debug
printf("ans:%d\n",ans);
#endif
printf("%d\n",a[ans]);
return 0;
}