CSP-S模拟8
Cat最喜欢清北营赛制了!但是这个赛制暗示了以下全是鬼畜题……
A. 选举
居然可以dp,我本来以为是贪心的题,联想到了学长提到的过关得到相应的星星,可以选择拿1颗或两颗,代价不同……于是我就把它按B排了个序,枚举有几个州得到协作者,剩下的再按A排个序,选A小的几个,然后只有10分。
然后我觉得可能定下了每个州的选择方案再排个序贪心就应该(一定)对了(非常不理解为什么不能直接贪,原来是最小的几个B不一定都必须选,如果有A远远小于B的话)[貌似连续选的话讨论一下最后一个不连续,特判一个7可以水过数据],于是我生成了n位三进制数表示方案,可以得到21分。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 505;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int n, k, m, cnt;
long double ans = 1e16, res;
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;
}
struct node
{
int a, b, v;
}p[maxn], ls[maxn], rs[maxn];
inline bool cmp1(node x, node y)
{
return x.b < y.b;
}
inline bool cmp2(node x, node y)
{
return x.a < y.a;
}
inline void init()
{
for(int i=1; i<=n; i++) p[i].v = 0;
res = 0;
}
inline int qpow(int a, int b)
{
int ans = 1;
while(b)
{
if(b & 1) ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
inline int get_num(int num)
{
int cnt = 0;
while(num)
{
if(num % 3) cnt++;
num /= 3;
}
return cnt;
}
int f3[maxn];
inline void check(int num)
{
res = 0; int op = 0, ol = 0;
/*for(int i=1; i<=n; i++)
{
if((num/f3[i-1])%3 == 2) printf("2 ");
else if((num/f3[i-1])%3 == 1) printf("1 ");
else printf("0 ");
}
printf("\n");*/
for(int i=1; i<=n; i++)
{
if((num/f3[i-1])%3 == 2)
{
if(p[i].b == -1) return;
ls[++op].a = p[i].a; ls[op].b = p[i].b;
}
else if((num/f3[i-1])%3 == 1)
{
rs[++ol].a = p[i].a; rs[ol].b = p[i].b;
}
}
sort(ls+1, ls+op+1, cmp1);
sort(rs+1, rs+ol+1, cmp2);
for(int i=1; i<=op; i++)
{
res += 1.0*ls[i].b/i;
}
for(int i=1; i<=ol; i++)
{
res += 1.0*rs[i].a/(op+1);
}
if(res < ans) ans = res;
}
void solve()
{
//int Max = qpow(3, n);
f3[0] = 1;
for(int i=1; i<=n; i++)
{
f3[i] = f3[i-1] * 3;
}
int Max = f3[n];
for(int i=0; i<Max; i++)
{
if(get_num(i) != k) continue;
check(i);
}
printf("%.15Lf\n", ans);
}
int main()
{
n = read(); k = read();
for(int i=1; i<=n; i++)
{
p[i].a = read(); p[i].b = read();
if(p[i].b != -1) cnt++;
}
if(n <= 7)
{
solve(); exit(0);
}
m = min(cnt, k);
for(int i=0; i<=m; i++)
{
init();
sort(p+1, p+1+n, cmp1);//B xiao first
//for(int j=1; j<=n; j++) printf("%d ", p[j].b);
//printf("\n");
int s = 1;
for(int j=1; j<=i; )
{
if(p[s].b == -1) {s++; continue;}
res += 1.0*p[s].b/j;
p[s].v = 1; j++; s++;
}
sort(p+1, p+1+n, cmp2);//A xiao first
/*for(int j=1; j<=n; j++)
{
printf("%d %d %d\n", p[j].a, p[j].b, p[j].v);
}*/
s = 1;
for(int j=i+1; j<=k; )
{
if(p[s].v) {s++; continue;}
res += 1.0*p[s].a/(i+1); j++;
p[s].v = 1;
s++;
}
/*for(int j=1; j<=n; j++)
{
if(p[j].v) printf("%d %d\n", p[j].a, p[j].b);
}
printf("i = %d res = %.15Lf\n", i, res);*/
if(res < ans) ans = res;
}
printf("%.15Lf\n", ans);
return 0;
}
以下来自dysyn大佬的题解https://www.cnblogs.com/dysyn1314/p/LOJ3664.html
T的dp:先把所有州按B排序,设f[i][j][k]表示考虑了前i个州,总共获得了j张选票,其中有k个州获得了协作者需要的最少时间,枚举总共会得到多少协作者,每个州有3种转移,要么不选用时不变,要么只获得选票(一定在获得所有协作者之后,所以时间的除数是所有人),要么同时获得选票和协作者(除数是当前协作者的数量+1)。
A的dp:设我们最终在a个州只获得了选票,在b个州获得了选票和协作者,在其余的c个州里什么也没有得到,记为A,B,C类州。考虑如果最后两个B类州中间如果有一个C类州,那么把最后一个B类州和这个C类州的种类互换会是答案更优,因为其他州的用时不变而最后一个B类州的用时减少了。所以任意两个B类州之间没有C类州,第一个B类州之间也没有C类州。
以最后一个B类州为界,序列的前半部分只有A类州或B类州,后半部分只有A类州或C类州。
f[i][j]表示考虑了前i个州,选定了j个B类州的最少用时,计算到最后一个B类州就停止,再在后半部分从小到大选择A类州,后半部分可以预处理求和也可以预处理到排好序为止。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 505;
const int N = 2e5 + 3;
const long double INF = 1e16;
int n, k, m, cnt;
long double ans = 1e16, res, f[maxn][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;
}
struct node
{
int a, b, v;
}p[maxn], ls[maxn], rs[maxn], sa[maxn][maxn];
inline bool cmp1(node x, node y)
{
return x.b < y.b;
}
inline bool cmp2(node x, node y)
{
return x.a < y.a;
}
inline void init()
{
for(int i=1; i<=n; i++) p[i].v = 0;
res = 0;
}
inline int qpow(int a, int b)
{
int ans = 1;
while(b)
{
if(b & 1) ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
inline int get_num(int num)
{
int cnt = 0;
while(num)
{
if(num % 3) cnt++;
num /= 3;
}
return cnt;
}
int f3[maxn];
inline void check(int num)
{
res = 0; int op = 0, ol = 0;
for(int i=1; i<=n; i++)
{
if((num/f3[i-1])%3 == 2)
{
if(p[i].b == -1) return;
ls[++op].a = p[i].a; ls[op].b = p[i].b;
}
else if((num/f3[i-1])%3 == 1)
{
rs[++ol].a = p[i].a; rs[ol].b = p[i].b;
}
}
sort(ls+1, ls+op+1, cmp1);
sort(rs+1, rs+ol+1, cmp2);
for(int i=1; i<=op; i++)
{
res += 1.0*ls[i].b/i;
}
for(int i=1; i<=ol; i++)
{
res += 1.0*rs[i].a/(op+1);
}
if(res < ans) ans = res;
}
void solve()
{
f3[0] = 1;
for(int i=1; i<=n; i++)
{
f3[i] = f3[i-1] * 3;
}
int Max = f3[n];
for(int i=0; i<Max; i++)
{
if(get_num(i) != k) continue;
check(i);
}
printf("%.15Lf\n", ans);
}
long double g[maxn][maxn];
int main()
{
n = read(); k = read();
for(int i=1; i<=n; i++)
{
p[i].a = read(); p[i].b = read();
if(p[i].b != -1) cnt++;
else p[i].b = 1e9 + 7;
}
/*if(n <= 7)
{
solve(); exit(0);
}*/
sort(p+1, p+1+n, cmp1);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=i; j++)
{
sa[i][j] = p[n-j+1];
}
sort(sa[i]+1, sa[i]+i+1, cmp2);
for(int j=1; j<=i; j++)
{
g[n-i+1][j] = g[n-i+1][j-1]+sa[i][j].a;
}
}
m = min(cnt, k);
for(int b=0; b<=m; b++)
{
for(int i=0; i<=n; i++)
{
for(int j=0; j<=n; j++)
{
f[i][j] = INF;
}
}
f[0][0] = 0;
for(int i=1; i<=m; i++)
{
for(int j=0; j<i&&j<b; j++)
{
f[i][j+1] = min(f[i][j+1], f[i-1][j]+(long double)p[i].b/(j+1));
f[i][j] = min(f[i][j], f[i-1][j]+(long double)p[i].a/(b+1));
}
}
for(int i=b; i<=m; i++)
{
/*long double last = 0;
for(int j=1; j<=k-i; j++)
{
last += (long double)sa[n-i][j].a/(b+1);
}
ans = min(ans, f[i][b]+last);*/
ans = min(ans, f[i][b]+g[i+1][k-i]/(b+1));
}
}
printf("%.15Lf\n", ans);
return 0;
}
B. 港口设施
看到求方案数就想到了计数类dp就想到了我绝对不会正解还是搜去吧***
然后我就生成了每个集装箱分在0栈或1栈,生成了一个n位二进制数来表示,然后check.
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2005;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int n, Max, ans, d[maxn];
int stk1[maxn], stk2[maxn], top1, top2;
//vector<pair<int, int> > t[maxn<<1];
pair<int, int> t[maxn<<1];
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++) stk1[i] = stk2[i] = 0;
top1 = top2 = 0;
for(int i=1; i<=n; i++)
{
if(num & (1<<(i-1))) d[i] = 0;
else d[i] = 1;
}
for(int i=1; i<=(n<<1); i++)
{
int x = t[i].first, y = t[i].second;
if(y == 1)
{
if(d[x] == 0) stk1[++top1] = x;
else stk2[++top2] = x;
}
else
{
if(d[x] == 0)
{
if(stk1[top1] != x) return 0;
else top1--;
}
else
{
if(stk2[top2] != x) return 0;
else top2--;
}
}
}
return 1;
}
int main()
{
n = read();
for(int i=1; i<=n; i++)
{
int x = read(), y = read();
//t[x].push_back(make_pair(i, 1)); t[y].push_back(make_pair(i, 0));
t[x] = make_pair(i, 1);
t[y] = make_pair(i, 0);
}
Max = (1 << n);
for(int i=0; i<Max; i++)
{
if(check(i)) ans = (ans + 1) % mod;
}
printf("%d\n", ans);
return 0;
}
其实好好思考一下就能发现这里面有一个二分图,每个二分图颜色互换就是两种方案,答案就是2^图中联通快的个数。于是n^2连边22 pts.
需要优化建边:维护一个集合S,如果该元素是作为左端点出现的那么将他放到集合中,如果是作为右端点就把它删去,每次出去一个点就把从它和从它进入的点开始还在集合S里的点连边,删去的操作就是用fa记录本来应该指向i的点在i被删除之后应该找到谁,nxt就是记录同色(就是和同一个点有连边)区间的右边界,防止重复连边。
同时和同色的点连边没有效果,因为同时连这一堆点的目的就是让他们同色。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 2;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int col[maxn], nxt[maxn], fa[maxn], le[maxn], id[maxn<<1], a[maxn], cnt, n;
ll ans;
vector<int> e[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;
}
ll qpow(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int find(int x)
{
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void add(int x, int y)
{
e[x].push_back(y); e[y].push_back(x);
}
int dfs(int u)
{
for(int i=0; i<e[u].size(); i++)
{
int v = e[u][i];
if(col[v]!=0 && col[v]==col[u]) return 0;
if(col[v] == 0)
{
col[v] = -col[u];
if(!dfs(v)) return 0;
}
}
return 1;
}
int main()
{
n = read();
for(int i=1; i<=n; i++)
{
int l = read(), r = read();
id[l] = id[r] = i;
}
for(int i=1; i<=n; i++) nxt[i] = i, fa[i] = i;
fa[n+1] = n+1;
for(int i=1; i<=2*n; i++)
{
int p = id[i];
if(!le[p]) a[++cnt] = p, le[p] = cnt;
else
{
int u = le[p];
fa[u] = find(u+1);
for(int j=fa[u],k; j<=cnt; j=k)
{
add(a[j], p); k = find(nxt[j]+1); nxt[j] = cnt;
}
}
}
for(int i=1; i<=n; i++)
{
if(col[i] == 0)
{
col[i] = 1;
if(dfs(i)) ans++;
else printf("0\n"), exit(0);
}
}
ans = qpow(2, ans);
printf("%lld\n", ans);
return 0;
}
C. 幽深府邸
本来打算直接bfs,结果发现每个点只走一次显然不对,每个点走无限次他又会出不来……
其实不用每次都重新走,可以把它看成向两个方向拓展可以到达的区间。l,r表示向右,向左走能找到通过从i到i+1和i到i-1的钥匙最近的位置,L,R表示从一个点拓展的左右边界,直接把每个点预处理出来,如果i一直往反方向走能找到这把钥匙并且钥匙的位置没有超出能到达的范围之外。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 2;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int n, q, l[maxn], r[maxn], id[maxn], ky[maxn], L[maxn], R[maxn];
int ps[maxn], c[maxn], ins[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 dfs(int x)
{
if(ins[x]) return;
ins[x] = 1;
L[x] = R[x] = x;
int ok = 1;
while(ok)
{
ok = 0;
int tl = L[x]-1, tr = R[x]+1;
if(r[tl] && r[tl] < tr)
{
dfs(tl);
if(L[tl] < L[x]) L[x] = L[tl], ok = 1;
if(R[tl] > R[x]) R[x] = R[tl], ok = 1;
}
if(l[tr] && l[tr] > tl)
{
dfs(tr);
if(L[tr] < L[x]) L[x] = L[tr], ok = 1;
if(R[tr] > R[x]) R[x] = R[tr], ok = 1;
}
}
}
int main()
{
n = read();
for(int i=1; i<n; i++) c[i] = read();
for(int i=1; i<=n; i++)
{
id[i] = id[i-1] + read();
for(int j=id[i-1]+1; j<=id[i]; j++)
{
ky[j] = read();
}
}
for(int i=n; i>1; i--)
{
for(int j=id[i-1]+1; j<=id[i]; j++)
{
ps[ky[j]] = i;
}
r[i-1] = ps[c[i-1]];
}
for(int i=1; i<=n; i++) ps[i] = 0;
for(int i=1; i<n; i++)
{
for(int j=id[i-1]+1; j<=id[i]; j++)
{
ps[ky[j]] = i;
}
l[i+1] = ps[c[i]];
}
for(int i=1; i<=n; i++) dfs(i);
q = read();
for(int i=1; i<=q; i++)
{
int x = read(), y = read();
if(L[x] <= y && y <= R[x]) printf("YES\n");
else printf("NO\n");
}
return 0;
}
D. 长途巴士
s,D在mod T的意义下都不重合
鹤题解:https://www.cnblogs.com/xxbbkk/p/14526445.html
如果将人按照Di升序排序,赶走的人一定是一段连续的区间。如果最后赶走的是第i个人,那么一定是某两个相邻的休息站a,b之间,第i个人是最后一个在到b之前要水喝的人。
设f[i]表示考虑了前i个人的最小费用,它的选择可以是他留到最后,或者他是被赶下车的人,因为排列后只考虑到i,所以他是最后一个。如果它是最后一个被赶下车的人,枚举他前面的人看看谁是最后一个留到最后的人,转移需要加上j+1~i下车的补偿和后面这批人在下车之前必须喝的水,需要求出他必须喝水次数的最小值。
每次下车的人数都是一段后缀,他们是同一次下车,所以可以用第i个人必须喝水次数的最小值表示j+1~i的每一个人。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5002;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int n, m;
ll X, w, T, s[maxn], f[maxn], mn[maxn], sum[maxn],ans = 1e18;
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;
}
struct node
{
ll d, c;
bool operator < (const node &T) const
{
return d < T.d;
}
}a[maxn];
int main()
{
X = read(); n = read(); m = read(); w = read(); T = read();
for(int i=1; i<=n; i++) s[i] = read();
s[++n] = X;
for(int i=1; i<=m; i++)
{
a[i].d = read(); a[i].c = read();
}
sort(a+1, a+1+m);
for(int i=1; i<=m; i++) sum[i] = sum[i-1] + a[i].c;
a[m+1].d = 1e13;
for(int i=1; i<=m; i++)
{
mn[i] = 1e13;
for(int j=1; j<=n; j++)
{
if((s[j]%T)>a[i].d && (s[j]%T)<a[i+1].d)//站点恰好在i之后
{
mn[i] = min(mn[i], s[j]/T);
//下文的最小满足j+1~i能被删的时间其实就是最小满足i被删的时间
//因为i是区间末尾,所以i被删的时间要求更苛刻,起决定作用
//从起点走到特殊的取水点的距离时j+1~i每个乘客必须要喝水次数的最小值
//因为他们是同一次被删掉的,所以最小值是一样的
}
}
}
//f[i]表示考虑了前i个人的最小费用
//要么把i留下,要么把i删了,那就去找上一个留下的乘客
for(int i=1; i<=m; i++)
{
f[i] = f[i-1]+w*((X-a[i].d)/T+1);
if(mn[i] == 1e13) continue;
for(int j=0; j<i; j++)
{
f[i] = min(f[i], f[j]+w*(i-j)*mn[i]+sum[i]-sum[j]);
}
}
printf("%lld\n", f[m]+(X/T+1)*w);
return 0;
}
关于斜率优化,由于我的公式过分丑陋,不自己写了。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 3;
const int N = 2e5 + 3;
const int mod = 1e9 + 7;
int n, m;
ll X, w, T, s[maxn], f[maxn], sum[maxn];
int stk[maxn], top;
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;
}
struct node
{
ll d, c;
bool operator < (const node &T) const
{
return d < T.d;
}
}a[maxn];
inline ll Y(int i) {return f[i]-sum[i];}
inline double K(int i, int j) {return (Y(i)-Y(j))/(i-j);}
inline int getpos(ll val)
{
int l = 1, r = top;
while(l < r)
{
int mid = (l + r) >> 1;
if(K(stk[mid], stk[mid+1]) > val) r = mid;
else l = mid + 1;
}
return stk[r];
}
inline bool cmp1(ll a, ll b) {return a%T < b%T;}
int main()
{
X = read(); n = read(); m = read(); w = read(); T = read();
for(int i=1; i<=n; i++) s[i] = read();
s[++n] = X;
for(int i=1; i<=m; i++)
{
a[i].d = read(); a[i].c = read();
}
sort(s+1, s+1+n, cmp1);
sort(a+1, a+1+m);
for(int i=1; i<=m; i++) sum[i] = sum[i-1]+a[i].c;
a[m+1].d = 1e13;
stk[++top] = 0;
for(int i=1,j=0; i<=m; i++)
{
f[i] = f[i-1]+((X-a[i].d)/T+1)*w;
ll mn = 1e13;
while(s[j+1]%T<a[i].d && j<n) j++;
while(s[j+1]%T<a[i+1].d && j<n) mn = min(mn, s[++j]/T);
if(mn != 1e13)
{
int pos = getpos(mn*w);
f[i] = min(f[i], f[pos]+(i-pos)*w*mn+sum[i]-sum[pos]);
}
while(top>1 && K(stk[top], stk[top-1])>K(stk[top], i)) top--;
stk[++top] = i;
}
printf("%lld\n", f[m]+(X/T+1)*w);
return 0;
}