2022-2023 ICPC, NERC, Southern and Volga Russian Regional Contest VP总结
若 \(a>b\),只需一次,若 \(a\le b\),需要 \(\lceil\frac{n}{a}\rceil\) 次。
#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;
}
// T = read();
void solve()
{
int n = read(), a = read(), b = read();
if(a > b) printf("1\n");
else printf("%d\n", (n + a - 1) / a);
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
等价于最大化 \(\gcd(a,b)\),找到 \(n\) 的最小质因子 \(d\),\(\frac{n}{d}\) 和 \(n-\frac{n}{d}\) 即为答案。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
int ans=1;
for(int i=2;i*i<=n;++i)
{
if(n%i==0)
{
ans=i;
break;
}
}
if(ans==1)
{
cout<<1<<' '<<n-1<<'\n';
}
else
{
cout<<n/ans<<' '<<n-n/ans<<'\n';
}
}
}
奇数次按键得到一个字母,偶数次按键得到两个字母,模拟即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define LL long long
#define endl '\n'
#define pii pair<int, int>
#define lowbit(x) (x & (-x))
const int N = 3e5 + 10;
int n, q, m, sum = 0;
pair<int, int> p[N];
void solve() {
cin >> n;
string s;
cin >> s;
int a[2] ={1, 2};
int j = 1;
bool f = 1;
for (int i = 0; i < s.size(); i += a[j]) {
j ^= 1;
if (j) {
if (i + 1 >= s.size() || s[i + 1] != s[i]) {
f = 0;
break;
}
}
}
if (f) cout << "YES" << endl;
else cout << "NO" << endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T--){
solve();
}
return 0;
}
// 1
// 5 3
// 2 3
// 1 5
// 4 5
最优情况是,在看这部视频时另一部视频正在下载,需要内存能够装下这两部视频,于是想到贪心的令相邻两部视频的内存都能够装在电脑里,不妨先将内存最大的视频下载,接下来不断找到能和前一个视频一起装在电脑里的内存最大的视频下载,若没有则放内存最大的视频。排序后二分。
这代码竟然能过?
#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, m;
int a[N];
void solve()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + n + 1);
ll sum = a[n];
int l = 1, r = n - 1, last = n;
while(l <= r)
{
if(a[r] + a[last] <= m) sum += a[r], last = r, --r;
else if(a[l] + a[last] <= m) sum += a[l], last = l, ++l;
else sum += a[r] + 1, last = r, --r;
}
printf("%lld\n", sum + 1);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
容易看出当走了一次 \((i,n)\to (i,1)\) 后,不能再进行 \((n,j)\to (1,j)\) 的操作。
于是考虑只进行 \((i,n)\to (i,1)\) 和向下向右的操作,另一种情况转置矩阵即可。
设 \(dp_{i,j}\) 表示从 \((i,j)\) 出发,下一步强制向下,走到 \((n,n)\) 的最高分。
转移时枚举最终走到了下一行的哪一个位置停下(下一步向下走)。预处理前后缀和加速转移。
#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 = 0x3f3f3f3f3f3f3f;
const int N = 205;
int n;
ll a[N][N], dp[N][N], sum[N][N], pre[N][N];
ll solve1()
{
for(int i = 0; i <= n + 1; ++i)
for(int j = 0; j <= n + 1; ++j)
dp[i][j] = -inf, sum[i][j] = 0, pre[i][j] = 0;
for(int i = 1; i <= n; ++i)
for(int j = n; j >= 1; --j)
sum[i][j] = sum[i][j + 1] + a[i][j];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
pre[i][j] = pre[i][j - 1] + a[i][j];
dp[n][n] = 0;
for(int i = n - 1; i >= 0; --i)
for(int j = 1; j <= n; ++j)
{
for(int k = j; k <= n; ++k)
{
dp[i][j] = max(dp[i][j], dp[i + 1][k] + sum[i + 1][j] - sum[i + 1][k + 1]);
}
for(int k = 1; k < j; ++k)
{
dp[i][j] = max(dp[i][j], dp[i + 1][k] + sum[i + 1][j] + pre[i + 1][k]);
}
}
return dp[0][1];
}
void solve()
{
n = read();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
a[i][j] = read();
ll ans1 = solve1();
for(int i = 1; i <= n; ++i)
for(int j = i + 1; j <= n; ++j)
swap(a[i][j], a[j][i]);
ll ans2 = solve1();
printf("%lld\n", max(ans1, ans2));
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
从高位开始贪心,不断寻找这一位可以得到的最小值。设 \(pos_{i,j}\) 表示后 \(i\) 个位置中,数字 \(j\) 出现的最早位置,加速寻找。
#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 = 5e5 + 5;
char s[N], t[N];
int K, tot, pos[N][10];
void solve()
{
scanf(" %s", s + 1);
int n = strlen(s + 1);
K = read();
int flag = 1;
for(int i = 2; i <= n; ++i)
{
if(i - 1 > K) break;
if(s[i] > '0' && s[i] < s[flag])
{
flag = i;
}
}
for(int i = 0; i <= 9; ++i) pos[n + 1][i] = n + 1;
for(int i = n; i >= 1; --i)
{
for(int j = 0; j <= 9; ++j) pos[i][j] = pos[i + 1][j];
pos[i][s[i] - '0'] = i;
}
K -= (flag - 1);
tot = 0;
t[++tot] = s[flag];
while(flag < n)
{
for(int i = 0; i <= 9; ++i)
{
if(pos[flag + 1][i] <= n && pos[flag + 1][i] - flag - 1 <= K)
{
t[++tot] = i + '0';
K -= pos[flag + 1][i] - flag - 1;
flag = pos[flag + 1][i];
break;
}
}
}
while(K) --tot, --K;
for(int i = 1; i <= tot; ++i) printf("%c", t[i]);
printf("\n");
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
Problem - A - Codeforces
最小路径覆盖
设 \(S_i\) 为可以访问文档 \(i\) 的人员集合,两个文档 \(i,j\) 可以放到同一组当且仅当 \(S_i\subseteq S_j\),考虑一种建边方式:若 \(S_i\subseteq S_j\),建有向边 \(j\to i\)。得到一个 \(DAG\),此时问题变为:在 \(DAG\) 上选取最少的路径覆盖所有点,且路径不交,是经典的最小路径覆盖问题。
最小路径覆盖做法:将 \(i\) 拆为 \(i\) 和 \(i+n\),对于原图中的边 \((u,v)\),建边 \((u,v+n)\),得到二分图,最小路径覆盖 = \(n\) - 最大匹配。
输出方案时,考虑最大匹配上的边 \((u,v+n)\),等价于 \(u\) 和 \(v\) 放到同一组。
一个小细节:若存在 \(S_i=S_j\),此时两个文档完全一样,可以先缩点,或者在建边时,强制小编号连向大编号(不建大编号连向小编号的边)
#include<bits/stdc++.h>
using namespace std;
const int INF=INT_MAX;
struct edge{
int v,flow,cup,nxt;
}e[2000005];
int cnt=1;
int head[2000005];
inline void add(int u,int v,int cup=1)
{
e[++cnt].v=v;
e[cnt].cup=cup;
e[cnt].nxt=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cup=0;
e[cnt].nxt=head[v];
head[v]=cnt;
}
int S,T,n,m;
int d[2000005],cur[2000005];
bool bfs()
{
queue<int>q;
q.push(S);
cur[S]=head[S];
memset(d,0,sizeof(d));
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(!d[v]&&v!=S&&e[i].cup>e[i].flow)
{
cur[v]=head[v];
d[v]=d[u]+1;
q.push(v);
if(v==T) return 1;
}
}
}
return 0;
}
int dfs(int u,int flow)
{
if(u==T) return flow;
int ret=0;
for(int i=cur[u];i;i=e[i].nxt)
{
int v=e[i].v;
cur[u]=i;
if(d[v]!=d[u]+1||e[i].cup==e[i].flow) continue;
int d=min(flow-ret,e[i].cup-e[i].flow);
d=dfs(v,d);
ret+=d;
e[i].flow+=d;
e[i^1].flow-=d;
if(ret==flow) return ret;
}
return ret;
}
// int MAX_FLOW()
// {
// int ans=0;
// while(bfs()) ans+=dfs(S,INF);
// return ans;
// }
int f[1005];
int gf(int x)
{
if(x==f[x]) return x;
return f[x]=gf(f[x]);
}
int merge(int x,int y)
{
int fx=gf(x),fy=gf(y);
f[fx]=fy;
}
inline int gc()
{
char ch=getchar();
while(ch<'0'||ch>'1') ch=getchar();
return ch=='1';
}
int mp[505][505];
bool check(int i,int j)
{
int cnt=0;
for(int k=1;k<=n;++k)
{
if(mp[k][j]&&!mp[k][i])
{
return 0;
}
if(mp[k][j]!=mp[k][i])++cnt;
}
if(!cnt&&i>j) return 0;
return 1;
}
bool eq(int i,int j)
{
for(int k=1;k<=n;++k)
{
if(mp[k][i]!=mp[k][j]) return 0;
}
return 1;
}
vector<int>V[1005];
int val[1005],col[1005],cnt2;
int gs(int i,int j)
{
int ans=1;
for(auto k:V[j])
{
if(mp[i][k])
{
ans=max(ans,val[k]);
}
}
return ans;
}
int main()
{
cin>>n>>m;
T=2*m+1;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
mp[i][j]=gc();
}
}
for(int i=1;i<=m;++i)
{
for(int j=1;j<=m;++j)
{
if(i==j) continue;
if(check(i,j))
{
add(i,j+m);
}
}
}
for(int i=1;i<=m;++i)
{
add(S,i);
add(i+m,T);
}
int ans=0;
while(bfs()) ans+=dfs(S,m+1);
cout<<m-ans<<endl;
for(int i=1;i<=m;++i) f[i]=i;
for(int u=1;u<=m;++u)
{
for(int i=head[u];i;i=e[i].nxt)
{
if(e[i].cup==e[i].flow&&e[i].v!=S)
{
merge(u,e[i].v-m);
}
}
}
for(int i=1;i<=m;++i)
{
V[gf(i)].push_back(i);
}
for(int i=1;i<=m;++i)
{
if(V[i].empty()) continue;
//puts("114");
++cnt2;
sort(V[i].begin(),V[i].end(),check);
int g=1;
val[V[i][0]]=++g;
col[V[i][0]]=cnt2;
for(int j=1;j<(int)V[i].size();++j)
{
if(!eq(V[i][j],V[i][j-1])) ++g;
val[V[i][j]]=g;
col[V[i][j]]=cnt2;
}
}
for(int i=1;i<=m;++i)
{
cout<<col[i]<<' ';
}
cout<<endl;
for(int i=1;i<=m;++i)
{
cout<<val[i]<<' ';
}
cout<<endl;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(gf(j)==j)
cout<<gs(i,j)<<' ';
}
cout<<endl;
}
}
非常战犯读错题了,以为让我求满足条件的情况下使拓扑序字典序最小。
我的解法:建边 \(a_i\to b_i\),得到 \(DAG\),将点按照 \(p_i\) 从小到大排序,依次将这些点加入拓扑序,考虑当前要将点 \(i\) 加入,对于支配着 \(i\) 且未加入拓扑序的点,只要求它们的顺序满足支配关系即可。设得到的拓扑序中,第 \(i\) 个点在 \(q_i\) 位置,第 \(i\) 个位置为 \(c_i\)。
设 \(X_{c_i}=p_i-q_i\)。
设支配着点 \(i\) 的点的集合为 \(S_i\)(包括点 \(i\)),记这些点为 \(1\) 类点,设满足 \(j\notin S_i,q_j<q_i\) 的点为 \(2\) 类点,若一个 \(2\) 类点 \(j\) 满足:\(k\in S_i,q_k>q_j\) 的点的个数不超过 \(X_j\),称其为特殊点,记有 \(k\) 个特殊点,则 \(i\) 的可能的最小排序位置为 \(q_i-k\)。
在 \(X_{c_i}\) 的定义下应该可以做我看错的那道题。
题解做法:
因为正序拓扑时,每选择一个点,都可能导致没有合法后继状态,考虑反向拓扑,当要求点 \(i\) 的最小排序位置时,在反向拓扑过程中一直拖着不放 \(i\),直到只能放 \(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 int inf = 0x3f3f3f3f;
const int N = 2e3 + 5;
int n, m, p[N];
vector<int> to1[N], to2[N];
int du[N], tuo[N], vis[N];
vector<int> S[N];
int c[N], q[N];
void dfs(int id, vector<int> &S)
{
if(vis[id]) return ;
S.emplace_back(id);
vis[id] = 1;
for(auto v : to2[id]) dfs(v, S);
}
int id[N];
bool cmp1(int x, int y){ return p[x] < p[y]; }
bool cmp2(int x, int y){ return tuo[x] < tuo[y]; }
int X[N];
int ans[N];
void solve()
{
n = read(), m = read();
for(int i = 1; i <= n; ++i) p[i] = read();
for(int i = 1; i <= m; ++i)
{
int a = read(), b = read();
to1[a].emplace_back(b);
++du[b];
to2[b].emplace_back(a);
}
queue<int> Q;
for(int i = 1; i <= n; ++i) if(du[i] == 0) Q.push(i);
int tot = 0;
while(!Q.empty())
{
int now = Q.front();
Q.pop();
tuo[now] = ++tot;
for(auto v : to1[now])
{
--du[v];
if(du[v] == 0) Q.push(v);
}
}
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j) vis[j] = 0;
dfs(i, S[i]);
sort(S[i].begin(), S[i].end(), cmp2);
}
for(int i = 1; i <= n; ++i) vis[i] = 0, id[i] = i;
sort(id + 1, id + n + 1, cmp1);
tot = 0;
for(int i = 1; i <= n; ++i)
{
if(vis[id[i]]) continue;
else
{
for(auto v : S[id[i]])
{
if(vis[v]) continue;
++tot;
c[tot] = v, q[v] = tot;
vis[v] = 1;
}
}
}
for(int i = 1; i <= n; ++i)
{
X[q[i]] = p[i] - q[i];
}
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j) vis[j] = 0;
for(auto x : S[i]) vis[q[x]] = 1;
for(int j = n - 1; j >= 1; --j) vis[j] += vis[j + 1];
int cnt = 0;
for(int j = q[i]; j >= 1; --j)
{
if(vis[j] != vis[j + 1]) continue;
if(X[j] < vis[j]) break;
else ++cnt;
}
ans[i] = q[i] - cnt;
}
for(int i = 1; i <= n; ++i) printf("%d ", ans[i]);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
将溶液看做二维平面上的点 \((x_i,c_i)\),则最终选择的溶液在二维平面上一定构成一个凸包。证明考虑两种溶液融合配成的溶液浓度及其价格在二维平面上是一条直线,若干直线取最大值即为凸包。
注意到答案即为凸包与 \(x\) 轴构成的面积乘以 \(k\)。一开始将价格 \(c_i\) 乘以 \(k\) 。
由于答案是凸包,所以一段浓度的溶液的价格只和相邻选择的两种溶液有关,首先将溶液按照浓度排序,设 \(dp_{i,j}\) 表示考虑完前 \(i\) 种溶液,上一个溶液为 \(j\) 时的最大期望利润。
转移时,若 \(j\neq i+1\),则 \(dp_{i+1,j}=dp_{i,j}\),直接继承。
若 \(j=i+1\),枚举上一个溶液为哪一个,计算梯形面积转移。
第一维可以直接压缩。
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
int x,y,w;
}p[5005];
bool cmp(node x,node y)
{
if(x.x==y.x) return x.y<y.y;
return x.x<y.x;
}
int dp[5005];
int add(int i,int j)
{
return (p[i].y+p[j].y)*(p[j].x-p[i].x);
}
signed main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;++i)
{
cin>>p[i].x>>p[i].w>>p[i].y;
p[i].y*=k;
p[i].w*=200ll;
}
sort(p+1,p+1+n,cmp);
for(int i=1;i<=n;++i)
{
dp[i]=-p[i].w;
}
//cout<<p[1].x<<' '<<p[1].y<<endl;
//dp[1]=-p[1].w;
int ans=0;
for(int i=1;i<=n;++i)
{
for(int j=i+1;j<=n;++j)
{
dp[j]=max(dp[j],dp[i]+add(i,j)-p[j].w);
}
ans=max(ans,dp[i]);
}
//cout<<ans<<endl;
printf("%.15Lf",(long double)ans/200.0);
//cout<<<<endl;
}
超级战犯读错题,导致队友爆写两小时假做法(
提供一种不使用并查集的做法。
考虑朴素的模拟过程:枚举天数,记录各个项目当前需要的工人,枚举工人寻找他做的项目或者枚举项目找做它的工人,复杂度瓶颈在于这个匹配过程中多次失配。
如果枚举工人时,他今天一定工作该多好。
记录各个项目当前需要的工人,若第 \(x\) 个项目需要第 \(i\) 个工人,将 \(x\) 插入小根堆 \(work_i\)。
维护 \(7\) 个 \(set\) 表示如果今天是星期一二三四五六七时,今天一定要工作的工人集合。
只要 \(work_i\) 不为空,第 \(i\) 个工人会在一周中他的工作日的 \(set\) 中出现。
所需要的修改只有删除/加入某个项目,根据 \(work_i\) 是否为空判断要不要在 \(set\) 中删去 \(i\)。
复杂度为单 \(\log\) 但是 \(7\) 倍常数。
#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, m, K;
int vis[8][N];
int h[N];
queue<int> q[N];
int ddl[N];
priority_queue< int, vector<int>, greater<int> > work[N]; // 每个人要做的项目
set<int> s[8]; // 某天需要出勤的人
void solve()
{
n = read(), m = read(), K = read();
for(int i = 1; i <= n; ++i)
{
int t = read();
while(t--)
{
string s;
cin >> s;
if(s == "Monday") vis[1][i] = 1;
else if(s == "Tuesday") vis[2][i] = 1;
else if(s == "Wednesday") vis[3][i] = 1;
else if(s == "Thursday") vis[4][i] = 1;
else if(s == "Friday") vis[5][i] = 1;
else if(s == "Saturday") vis[6][i] = 1;
else if(s == "Sunday") vis[7][i] = 1;
}
}
for(int i = 1; i <= m; ++i) h[i] = read();
for(int i = 1; i <= K; ++i)
{
int p = read();
while(p--)
{
int x = read();
q[i].emplace(x);
}
int x = q[i].front();
work[x].emplace(i);
for(int j = 1; j <= 7; ++j) if(vis[j][x]) s[j].insert(x);
}
int cnt = 0;
for(int i = 1; cnt < K; ++i)
{
if(h[lower_bound(h + 1, h + m + 1, i) - h] == i) continue;
int t = (i - 1) % 7 + 1;
vector<int> t1, t2;
for(auto x : s[t]) t1.emplace_back(x);
for(auto x : t1)
{
assert(!work[x].empty());
int y = work[x].top();
work[x].pop();
q[y].pop();
if(!q[y].empty()) t2.emplace_back(y);
else ddl[y] = i, ++cnt;
if(work[x].empty()) for(int j = 1; j <= 7; ++j) if(vis[j][x]) s[j].erase(x);
}
for(auto y : t2)
{
int x = q[y].front();
for(int j = 1; j <= 7; ++j) if(vis[j][x]) s[j].insert(x);
work[x].emplace(y);
}
}
for(int i = 1; i <= K; ++i) printf("%d ", ddl[i]);
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号