湖南多校5
J 签到
A 模拟
Problem - M - Codeforces
做过加强版:AT_abc262_h [ABC262Ex] Max Limited Sequence - 洛谷
直接沿用 \(dp_{i,j}\) 表示考虑完前 \(i\) 个位置,上一个取到的位置为 \(j\) 且满足右端点 \(r_x\le i\) 的区间的最大代价。设 \(pos_i\) 表示 \(\max_{r_x\le i} l_x\)。
转移时考虑 \(i\neq j\),则 \(dp_{i,j}=dp_{i-1,j}\)。
若 \(i=j\),则 \(dp_{i,i}=\min_{pos_{i-1}\le k<i}dp_{i-1,k}+c_i\)。
线段树维护即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 5;
int n, m;
int c[N];
struct node{ int l, r; } a[N], b[N];
int pos[N];
bool cmp(node &a, node &b)
{
return (a.l == b.l ) ? (a.r < b.r ) : a.l < b.l ;
}
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
ll dp[N << 2], lazy[N << 2];
void pushup(int k){ dp[k] = min(dp[ls(k)], dp[rs(k)]); }
void pushdown(int k)
{
if(lazy[k] == inf) return ;
dp[ls(k)] = dp[rs(k)] = lazy[k];
lazy[ls(k)] = lazy[rs(k)] = lazy[k];
lazy[k] = inf;
}
void build(int k, int l, int r)
{
dp[k] = lazy[k] = inf;
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls(k), l, mid), build(rs(k), mid + 1, r);
}
void update(int k, int l, int r, int L, int R, ll val)
{
if(L <= l && r <= R)
{
dp[k] = lazy[k] = val;
return ;
}
pushdown(k);
int mid = (l + r) >> 1;
if(L <= mid) update(ls(k), l, mid, L, R, val);
if(R > mid) update(rs(k), mid + 1, r, L, R, val);
pushup(k);
}
ll query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return dp[k];
pushdown(k);
int mid = (l + r) >> 1;
if(R <= mid) return query(ls(k), l, mid, L, R);
if(L > mid) return query(rs(k), mid + 1, r, L, R);
return min(query(ls(k), l, mid, L, R), query(rs(k), mid + 1, r, L, R));
}
void get()
{
build(1, 0, n);
update(1, 0, n, 0, 0, 0);
for(int i = 1; i <= n; ++i)
{
ll x = query(1, 0, n, pos[i - 1], i - 1);
if(pos[i - 1] < pos[i]) update(1, 0, n, pos[i - 1], pos[i] - 1, inf - 1);
update(1, 0, n, i, i, x + c[i]);
}
printf("%lld\n", query(1, 0, n, 0, n));
}
void solve()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) c[i] = read();
for(int i = 1; i <= m; ++i)
{
a[i].l = read(), a[i].r = read();
pos[a[i].r ] = max(pos[a[i].r ], a[i].l );
}
sort(a + 1, a + m + 1, cmp);
int tot = 1;
b[1] = a[1];
for(int i = 2; i <= n; ++i)
{
if(a[i].l == b[tot].l ) continue;
b[++tot] = a[i];
}
for(int i = 1; i <= n; ++i) pos[i] = max(pos[i], pos[i - 1]);
get();
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
只需要关注最后一位。
思考后发现对于一开始就能向下取整的直接向下取整最优,此时剩下若干个 \(3\),\(4\),分类讨论得到最优策略为先按照 \(3+4\) 匹配,之后按照 \(3+3\) 或者 \(4+4+4\) 匹配,不能匹配的刷卡支付。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
int n;
int cnt[N];
void solve()
{
ll sum = 0;
n = read();
for(int i = 1; i <= n; ++i)
{
double x;
scanf(" %lf", &x);
ll y = (ll)(x * 100 + 0.5);
ll z = y % 5;
if(z <= 2) sum += y - z;
else
{
++cnt[z];
sum += y - z;
}
}
int t = min(cnt[3], cnt[4]);
sum += t * 5;
cnt[3] -= t, cnt[4] -= t;
if(cnt[3])
{
int xx = cnt[3] / 2;
sum += xx * 5;
cnt[3] -= xx * 2;
sum += cnt[3] * 3;
}
if(cnt[4])
{
int xx = cnt[4] / 3;
sum += xx * 10;
cnt[4] -= xx * 3;
sum += 4 * cnt[4];
}
printf("%d.%02d\n", sum / 100, sum % 100);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
重要性质(认真读题读出来的)一张通用票从第一次开始使用后,会涵盖后面的连续的旅行计划,即不会出现一张票在第 \(i\) 次旅行使用,第 \(i+2\) 次旅行使用,第 \(i+1\) 次旅行不使用的情况。
设 \(dp_i\) 表示完成前 \(i\) 次旅行计划所需的最小话费,枚举接下来买哪一张通用票或者单独买下一场的票,转移是一个区间取 \(min\),线段树维护。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e4 + 5, M = 105;
int n, K;
int t[N], f[N];
int p[M], d[M], c[M];
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
ll dp[N << 2], lazy[N << 2];
void pushup(int k)
{
dp[k] = min(dp[ls(k)], dp[rs(k)]);
}
void pushdown(int k)
{
if(lazy[k] == inf) return ;
dp[ls(k)] = min(dp[ls(k)], lazy[k]);
dp[rs(k)] = min(dp[rs(k)], lazy[k]);
lazy[ls(k)] = min(lazy[ls(k)], lazy[k]);
lazy[rs(k)] = min(lazy[rs(k)], lazy[k]);
lazy[k] = inf;
}
void build(int k, int l, int r)
{
dp[k] = lazy[k] = inf;
if(l == r) return ;
int mid = (l + r) >> 1;
build(ls(k), l, mid), build(rs(k), mid + 1, r);
}
void update(int k, int l, int r, int L, int R, ll val)
{
if(L <= l && r <= R)
{
dp[k] = min(dp[k], val);
lazy[k] = min(lazy[k], val);
return ;
}
pushdown(k);
int mid = (l + r) >> 1;
if(L <= mid) update(ls(k), l, mid, L, R, val);
if(R > mid) update(rs(k), mid + 1, r, L, R, val);
pushup(k);
}
ll query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return dp[k];
pushdown(k);
int mid = (l + r) >> 1;
if(R <= mid) return query(ls(k), l, mid, L, R);
if(L > mid) return query(rs(k), mid + 1, r, L, R);
return min(query(ls(k), l, mid, L, R), query(rs(k), mid + 1, r, L, R));
}
void solve()
{
n = read(), K = read();
for(int i = 1; i <= n; ++i)
{
t[i] = read(), f[i] = read();
}
for(int i = 1; i <= K; ++i)
{
p[i] = read(), d[i] = read(), c[i] = read();
}
build(1, 0, n);
update(1, 0, n, 0, 0, 0);
for(int i = 0; i < n; ++i)
{
ll x = query(1, 0, n, i, i);
update(1, 0, n, i + 1, i + 1, x + f[i + 1]);
for(int k = 1; k <= K; ++k)
{
int l = i + 1, r = n;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(mid - i > d[k] || t[mid] - t[i + 1] + 1 > p[k]) r = mid - 1;
else l = mid;
}
update(1, 0, n, i + 1, l, x + c[k]);
}
}
printf("%lld\n", query(1, 0, n, n, n));
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
Problem - E - Codeforces
数学 二分
由于 \(O_i\) 升序排序,一定选择 \(O_1\) 和 \(O_n\),再选择一个最接近 \(x=\frac{O_1+O_n}{2}\) 的数,二分查找第一个大于 \(x\) 的位置 \(l\),从 \(l-1\) 和 \(l\) 中取最优。
#include<bits/stdc++.h>
using namespace std;
int query(int l)
{
int x;
cout<<"? "<<l<<endl;
cin>>x;
return x;
}
#define ld long double
int main()
{
int n;
cin>>n;
int a1=query(1);
int an=query(n);
int val=(a1+an)/2;
int l=2,r=n-1;
while(r>l)
{
int mid=(l+r)/2;
int k=query(mid);
if(k>val)
{
r=mid;
}
else
{
l=mid+1;
}
}
if(l==2)
{
cout<<"! "<<1<<' '<<l<<' '<<n<<endl;
}
else
{
int k=query(l);
long double ans1=sqrt(an-k)+sqrt(k-a1);
int k2=query(l-1);
long double ans2=sqrt(an-k2)+sqrt(k2-a1);
if(ans1>ans2)
{
cout<<"! "<<1<<' '<<l<<' '<<n<<endl;
}
else
{
cout<<"! "<<1<<' '<<l-1<<' '<<n<<endl;
}
}
}
对于每一种子串,记录所有的endpos,从这些endpos中取一些位置,相邻两个位置差大于等于这个子串的长度,贪心取位置即可。
如何求endpos,枚举所有子串,将它的哈希值映射到一个编号,也不必将所有endpos记录下来,按照先 \(len\) 再左端点 \(l\) 的方式遍历子串,能保证对于每一种子串,得到的endpos是单调递增的,直接开始贪心,记录上一次这个子串的endpos的位置。
学到了:vector在建立时占用6个int的空间,本题开 \(n^2\) 个vector会直接爆空间。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const ull base = 13331;
const int N = 5e3 + 5;
char s[N];
ull H[N], B[N];
unordered_map< ull, int > mp[N];
int tot;
int cnt[N * N], last[N * N], L[N * N];
void solve()
{
scanf(" %s", s + 1);
int n = strlen(s + 1);
B[0] = 1;
for(int i = 1; i <= n; ++i) H[i] = H[i - 1] * base + s[i], B[i] = B[i - 1] * base;
for(int len = 1; len <= n; ++len)
for(int l = 1, r = len; r <= n; ++l, ++r)
{
ull t = H[r] - H[l - 1] * B[len];
int id = 0;
if(mp[len].find(t) != mp[len].end()) id = mp[len][t];
else id = mp[len][t] = ++tot;
L[id] = len;
if(last[id] == 0 || last[id] + len <= r) ++cnt[id], last[id] = r;
}
int ans = n + 1;
for(int i = 1; i <= tot; ++i)
{
ans = min(ans, n - cnt[i] * L[i] + cnt[i] + L[i]);
}
printf("%d\n", ans);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
Problem - F - Codeforces
强联通分量
对图求强联通分量后,发现如果一个强联通分量中的一个步骤失败,整个强联通分量的步骤都会失败,所以成功当且仅当所有点都成功。缩点后得到 \(DAG\),选择入度为 \(0\) 的点一定最优。
学到了:cout会自动控制精度,会将0.1+1e-7判定为0.1,可能导致将精确值视为精度误差舍去,因此以后再输出浮点数时,不论用cout还是printf都要限制小数位数。本题使用printf("%.250Lf")通过。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int inf=1e9+7;
#define M(A,B) make_pair(A,B)
#define ld long double
const int N = 2e5 + 5;
int n, m;
ld a[N], p[N];
vector<int>E[N],scc[N];
int dfn[N], low[N], dfncnt, s[N], in_stack[N], tp, in[N];
int sc, col[N], siz[N];
void tarjan(int u) {
low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
for (int i :E[u]) {
int v = i;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
++sc;
do {
scc[sc].push_back(s[tp]);
in_stack[s[tp]] = 0;
col[s[tp]] = sc;
} while (s[tp--] != u);
}
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) {
int a, b; cin >> a >> b;
E[b].push_back(a);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
for (int i = 1; i <= n; i++) {
for (int j : E[i]) {
if (col[i] == col[j]) continue;
in[col[j]]++;
}
}
ld ans = 0;
for (int i = 1; i <= sc; i++) {
if (!in[i]) {
p[i] = 1;
for (int j : scc[i]) {
p[i] = p[i] * ((ld)(1.0) - a[j]);
}
ans = max(ans, p[i]);
}
}
printf("%.250Lf\n", ans);
}
猜结论,按照能力值从大到小考虑,每个人都选择当前对自己最优的比赛,我不会证。
怎么跟高考志愿一样,分数高的人先选,分数低的人不影响他的决策,如果一个分数高的想换志愿,分数比他低的人也想换志愿,所以从大到小贪心就对(?)
#include<bits/stdc++.h>
using namespace std;
#define int long long
pair<int,int>p[200005];
int n,m;
int val[200005],t[200005];
int ans[200005];
vector<int>v[205];
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>p[i].first;
p[i].second=i;
}
for(int i=1;i<=m;++i)
{
cin>>val[i];
}
sort(p+1,p+1+n);
for(int i=n;i>=1;--i)
{
int mxi=1;
for(int j=2;j<=m;++j)
{
if(val[j]*(t[mxi]+p[i].first)>val[mxi]*(t[j]+p[i].first))
{
mxi=j;
}
}
v[mxi].push_back(p[i].second);
t[mxi]+=p[i].first;
}
for(int i=1;i<=m;++i)
{
cout<<v[i].size()<<' ';
for(auto j:v[i])
{
cout<<j<<' ';
}
cout<<endl;
}
}
Problem - B - Codeforces
榜有点歪啊,一眼秒了,但是开题太晚了写不完了。
发现每一个点的状态只有当前朝向哪边和打左/右闪/不打闪至多12种,换转向灯的转移边权为0,移动到下一个点的转移边权为1,01BFS即可。
设 \(d(i,j)\) 表示从 \((i,j)\) 往上有 \(d(i,j)\) 个 .,考虑矩形高度为 \(x\),将 \(d(i,j)\ge x\) 的位置在每一行并查集,得到一些极长线段,设 \(I_y\) 表示长度为 \(y\) 的极长线段个数,那么矩形 \(x\times y\) 的个数 \(w_y=I_y+2I_{y+1}+\cdots\)。
设 \(c_y=I_y+I_{y+1}+\cdots\),有 \(c_y=I_y+c_{y+1},w_y=c_y+w_{y+1}\)。
实际上是枚举了最后一行的位置,保证高度的情况下对一种长度计数。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 9e6 + 5;
int r, c, q;
string s[N];
vector< pair<int, int> > h[N];
vector< pair<int, int> > p[N];
ll I[N], C[N], R[N], ans[N];
int f[N], Size[N];
int find(int x){ return (f[x] == x) ? f[x] : (f[x] = find(f[x])); }
void merge(int x, int y)
{
x = find(x), y = find(y);
if(x == y) return ;
f[y] = x, Size[x] += Size[y];
}
void solve()
{
r = read(), c = read(), q = read();
for(int i = 1; i <= r; ++i) cin >> s[i];
for(int i = 1; i <= q; ++i)
{
int x = read(), y = read();
h[x].emplace_back(pair<int, int>(y, i));
}
for(int i = 1; i <= c; ++i)
{
int x = 0;
for(int j = 1; j <= r; ++j)
{
if(s[j][i - 1] == '#') x = 0;
else ++x;
p[x].emplace_back(pair<int, int>(j, i));
}
}
for(int i = r * c; i >= 1; --i) f[i] = i, Size[i] = 0;
for(int i = r; i >= 1; --i)
{
for(auto [x, y] : p[i])
{
Size[(x - 1) * c + y] = 1;
if(y > 1 && Size[(x - 1) * c + y - 1])
{
int fa = find((x - 1) * c + y - 1);
I[Size[fa]]--;
merge(fa, (x - 1) * c + y);
}
if(y < c && Size[(x - 1) * c + y + 1])
{
int fa = find((x - 1) * c + y + 1);
I[Size[fa]]--;
merge(fa, (x - 1) * c + y);
}
int fa = find((x - 1) * c + y);
I[Size[fa]]++;
}
for(int j = c; j >= 1; --j)
{
C[j] = C[j + 1] + I[j];
R[j] = C[j] + R[j + 1];
}
for(auto [x, id] : h[i])
{
ans[id] = R[x];
}
}
for(int i = 1; i <= q; ++i) printf("%lld\n", ans[i]);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号