CSP-S模拟9
It's not until you fall that you fly…
A. 最长上升子序列
有一篇题解的图很形象的描绘了假贪心和正解的差别:https://blog.csdn.net/weixin_45911397/article/details/119860304
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const long double INF = 1e16;
int a[maxn], n, k, c[maxn], cnt;
bool v[maxn];
inline int read()
{
int 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read(); k = read();
for(int i=1; i<=k; i++)
{
a[i] = read(); v[a[i]] = 1;
}
for(int i=1; i<=n; i++)
{
if(v[i]) continue;
else c[++cnt] = i;
}
int num = 1;
for(int i=1; i<k; i++)
{
printf("%d ", a[i]);
if(num<=cnt && c[num]<a[i]) printf("%d ", c[num++]);
}
while(num<=cnt && c[cnt]>a[k]) printf("%d ", c[cnt--]);
printf("%d ", a[k]);
while(num<=cnt) printf("%d ", c[cnt--]);
printf("\n");
return 0;
}
B. 独特序列
Cat是怎么错的:我想到了设f[i]表示A中考虑到i并且必须选上i的方案数,然后找到没有后继的i更新答案,但是关于转移(从j到i)我只想到了i到j这段区间中不能出现a[i],其实a[j]也不能出现,由于没有发现a[j]的问题,我发现可以直接累计前缀和相减,而且还不包括pre[i],我把它们单独拿出来算了,因为出现多次的数只有全部出现才可以合法,所以后面加上答案2^只出现一次的数的个数,但是多次出现的数虽然不能作为答案但是可以参与转移,不能遇到重复就跳过。
当Cat调了一个上午发现i到j出现a[j]不对的时候,距离考试结束还有20 min!?于是就急忙的写了个暴力,T3T4还没来得及仔细审题***
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;
int n, a[maxn], d[maxn], ans, Max;
int p[maxn], l[maxn], r[maxn];
inline int read()
{
int 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
bool check(int num)
{
for(int i=1; i<=n; i++)
{
if(num & (1<<(i-1))) d[i] = 1;
else d[i] = 0;
}
//for(int i=1; i<=n; i++) printf("%d ", d[i]);
//printf("\n");
int las = 0;
for(int i=1; i<=n; i++)
{
if(!d[i]) continue;
if(l[i] > las) return 0;
las = i;
/*int j = i-1;
while(!d[j] && j>0)
{
if(a[j] == a[i]) return 0;
j--;
}
j = i+1;
while(!d[j] && j<=n)
{
if(a[j] == a[i]) return 0;
j++;
}*/
}
las = n+1;
for(int i=n; i>=1; i--)
{
if(!d[i]) continue;
if(r[i] && r[i] < las) return 0;
las = i;
}
return 1;
}
int main()
{
n = read();
for(int i=1; i<=n; i++) a[i] = read();
//
for(int i=1; i<=n; i++)
{
l[i] = p[a[i]]; p[a[i]] = i;
}
for(int i=1; i<=n; i++) p[i] = 0;
for(int i=n; i>=1; i--)
{
r[i] = p[a[i]]; p[a[i]] = i;
}
Max = 1 << n;
for(int i=1; i<Max; i++)
{
if(check(i)) ans++;
}
ans %= mod;
printf("%d\n", ans);
return 0;
}
所以正解就是要在删除不合法的a[j]在j~i(开区间)出现过的不合法的f[j]的情况下保证时间,暴力判断的话TLE 45,可以用树状数组维护前缀和并进行单点修改。初始化f[0]=1在树状数组上的体现就是把下标整体右移,所以query(i)-query(pre[i])想表达的其实是query(i-1)-query(pre[i]-1),重复的a[i]可以恰好出现在转移点上,f[pre[i]]是计算在内的。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;
int n, a[maxn], nxt[maxn], pre[maxn], las[maxn];
ll c[maxn], ans, f[maxn];
inline int read()
{
int 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
void add(int x, ll val)
{
while(x <= n)
{
c[x] += val;
x += (x&-x);
}
}
ll query(int x)
{
ll ans = 0;
while(x)
{
ans += c[x];
x -= (x&-x);
}
return ans;
}
int main()
{
n = read();
for(int i=1; i<=n; i++)
{
a[i] = read();
if(las[a[i]])
{
nxt[las[a[i]]] = i; pre[i] = las[a[i]];
}
las[a[i]] = i;
}
add(1, 1);
for(int i=1; i<=n; i++)
{
f[i] = (query(i)-query(pre[i]))%mod;
if(pre[i]) add(pre[i]+1, -f[pre[i]]);
if(!nxt[i]) ans = (ans+f[i])%mod;
add(i+1, f[i]);
}
printf("%lld\n", ans);
return 0;
}
C. 最大GCD
当我看到数据范围,我以为暴力不可能有分还不如去调T2,于是直到考试结束我还在写T2……
结果最简单的暴力TLE 45,二分乱搞好像也很优秀,小于枚举的gcd的a预处理前缀和剩下的暴力计算TLE 60……我也不知道我应不应该后悔去搞了一上午的T2呢***
code TLE 45
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;
ll n, k, gcc, a[maxn], mx, ans, tot;
inline ll read()
{
ll 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
bool check(int num)
{
ll ned = 0;
//printf("check(%d)\n", num);
for(int i=1; i<=n; i++)
{
ll w = a[i] % num;
//printf("w = %lld\n", w);
if(w)
{
ned += num - w;
if(ned > k) return 0;
}
}
return 1;
}
int main()
{
n = read(); k = read();
for(int i=1; i<=n; i++)
{
a[i] = read(); mx = max(a[i], mx);
}
for(int i=1; i<=n; i++)
{
tot += mx - a[i];
}
if(k >= tot)
{
k -= tot;
ans = mx + k / n;
printf("%lld\n", ans);
exit(0);
}
for(int i=mx; i>=1; i--)
{
if(check(i))
{
printf("%d\n", i);
break;
}
}
return 0;
}
https://blog.csdn.net/weixin_45483201/article/details/120442178
判断这个东西合不合法,最主要的优化在于分段求和,由于特定区间向上取整的值是一样的,总数不随枚举的gcd而变化,就可以先预处理再分段枚举了。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 2;
const int N = 2e5 + 3;
const ll mod = 998244353;
int a[maxn], cnt[maxn], n, Max;
ll sum[maxn], k, s, ans;
inline ll read()
{
ll 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read(); k = read();
for(int i=1; i<=n; i++)
{
a[i] = read();
s += a[i]; cnt[a[i]]++; sum[a[i]] += a[i];
Max = max(Max, a[i]);
}
if(1ll*Max*n-s <= k)
{
ans = (k+s)/n;
printf("%lld\n", ans); exit(0);
}
for(int i=1; i<=Max; i++)
{
cnt[i] += cnt[i-1];
sum[i] += sum[i-1];
}
ans = 1;
for(int i=2; i<Max; i++)
{
ll res = 0;
for(int j=i; j<=Max; j+=i)
{
res += 1ll*(cnt[j]-cnt[j-i])*j-(sum[j]-sum[j-i]);
}
if(Max % i)
{
int l = Max/i*i, r = Max;
res += 1ll*(cnt[r]-cnt[l])*(Max/i+1)*i-(sum[r]-sum[l]);
}
if(res <= k) ans = i;
}
printf("%lld\n", ans);
return 0;
}
D. 连续子段
我一开始打算把每次交换后的状态扔进去bfs,结果用map判重过不了编译原来是定义结构体排序的问题,我又想把它们先转string再bfs,结果读入数据和判断结束的依据都是个大问题,把数字直接放进sting居然输出一堆乱码……于是我放弃了***回去写T2了
https://www.cnblogs.com/gtm1514/p/16719577.html
鹤了%%%gtm1514的题解感觉良好
尽管粘了链接还是要抄一遍:
设f[i][j]表示扫描到i,找到的1~k的数的集合为j的最小步数,有两种转移:
1.如果扫到的数没在集合里就把它加进来,需要的步数是集合和这个数组成的逆序对数,也就是j中比a大的数的个数,也就是j中高于第a位的为1的位的个数。
2.不加入扫到的数,它可能是新的也可能不是,不过它都是空位,最终连续的k个数里是不能有空位的,所以要么是选上的数向右平移,要么是没选上的数向左平移,可以预处理最小值。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5002;
const int N = 2e5 + 3;
const int INF = 0x3f3f3f3f;
int n, k, f[(1<<16)+5], cnt[(1<<16)+5], t[(1<<16)+5];
inline int read()
{
int 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 << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read(); k = read();
for(int j=1; j<(1<<k); j++)
{
cnt[j] = cnt[j>>1] + (j&1);
t[j] = min(cnt[j], k-cnt[j]);
}
for(int j=1; j<(1<<k); j++) f[j] = INF;
for(int i=1; i<=n; i++)
{
int a = read()-1;
for(int j=(1<<k)-1; j>=0; j--)
{
if(f[j] != INF)
{
if(!(j&(1<<a))) f[j|(1<<a)] = min(f[j|(1<<a)], f[j]+cnt[j>>a]);
f[j] += t[j];
}
}
}
printf("%d\n", f[(1<<k)-1]);
return 0;
}