ABC210~214
abc210
D
动态规划
很普通的 dp 题,设计状态 \(f_{i,j}\) 为目前只设了一个车站的最小花费,每次转移要么更改车站位置要么从左边或上边的车站走过来,同时记录答案即可。
由于初始车站只会在终点车站的左上角,所以我们还需要将整个 \(a\) 数组反转一下重新 dp 一遍。
#define int long long
const int N = 1e3+10;
int a[N][N],ans = 1e18,f[N][N];
inline void solve()
{
int n,m,c;
auto work = [&]()
{
memset(f,0x3f,sizeof(f));
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
{
f[i][j] = std::min({a[i][j],f[i][j-1]+c,f[i-1][j]+c});
ans = std::min({ans,f[i][j-1]+a[i][j]+c,f[i-1][j]+a[i][j]+c});
}
};
fin >> n >> m >> c;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
fin >> a[i][j];
work();
for(int i = 1;i <= n;i++)
std::reverse(a[i]+1,a[i]+1+m);
work();
fout << ans;
}
E
数论
找性质的题,
发现若 \(gcd^m_1 a[i].a > 1\) ,则无解。
对于剩余情况,我们贪心的来求,将 \(a\) 数组按照 \(b\) 属性的大小来排序,
发现每次原数组的连通块数量会变成 \(gcd(a[i].a,n)\) 。
这样我们还可以知道我们一共连了 $n - gcd(a[i].a,n) $ 条边。
将权值 \(c\) 加入到计算当中,模拟即可。
struct node{int a,b;} a[N];
inline void solve()
{
int n,m; fin >> n >> m;
for(int i = 1;i <= m;i++)
fin >> a[i].a >> a[i].b;
std::sort(a+1,a+1+m,[](node x,node y){
return x.b < y.b;
});
int ans = 0;
for(int i = 1;i <= m;i++)
{
ans += (n - std::__gcd(n,a[i].a)) * a[i].b;
n = std::__gcd(n,a[i].a);
if(n == 1) break;
}
if(n > 1) fout << -1;
else fout << ans ;
}
F
2-SAT + 图论建模
第一次在 abc 看到 2-SAT 。
我们需要先将每个数质因数分解,显然拥有同一个质数的数不能同时存在,对这个约束建图。
但是这样直接建图的话复杂度会来到 \(O(n^2)\)。
考虑前后缀优化建图,不会的去写https://www.luogu.com.cn/problem/P6378。
建完图直接跑 2-SAT 就好了。
#define eb emplace_back
const int N = 2e6+10;
std::bitset<N> st;
int p[N],pp = 0,mnp[N];
int d[N],rk[N],n;
inline void euler(int x)
{
for (int i = 2; i <= x; i++)
{
if (!st[i]) p[++pp] = i,mnp[i] = i,rk[i] = pp;
for (int j = 1; p[j] * i <= x; j++)
{
st[p[j] *i] = 1;
mnp[i*p[j]] = p[j];
if (i % p[j] == 0)break;
}
}
}
std::vector<int> E[N],t[N];
void add(int x,int y){E[x].eb(y);}
std::stack<int> q;
int dfn[N],low[N],in[N],bel[N],scc,tot;
void tarjan(int u)
{
low[u]=dfn[u]=++tot;
q.push(u);
in[u]=1;
for(int v : E[u])
{
if(!dfn[v])
{
tarjan(v);
low[u] = std::min(low[u],low[v]);
}
else if(in[v])
low[u] = std::min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
scc++;
while(!q.empty())
{
if(q.top()==u)
{
in[u]=0;
bel[u]=scc;
q.pop();
break;
}
in[q.top()] = 0;
bel[q.top()] = scc;
q.pop();
}
}
}
int fd(int x){return x > n ? x - n : x + n;}
inline void solve()
{
euler(2000000);
fin >> n;
int m = 2*n;
for(int i = 1;i <= n;i++) fin >> d[i] >> d[i+n];
for(int i = 1;i <= m;i++)
{
int tmp = d[i];
while(tmp > 1)
{
while(tmp / mnp[tmp] % mnp[tmp] == 0)
tmp /= mnp[tmp];
t[rk[mnp[tmp]]].emplace_back(i);
tmp /= mnp[tmp];
}
}
for(int i = 1;i <= pp;i++)
{
if(t[i].empty()) continue;
add(++m,fd(t[i][0]));
for(int j = 1;j < t[i].size();j++)
add(t[i][j],m++),
add(m,m-1),
add(m,fd(t[i][j]));
add(++m,fd(t[i].back()));
for(int j = t[i].size()-2;j >= 0;j--)
add(t[i][j],m++),
add(m,m-1),
add(m,fd(t[i][j]));
}
for(int i = 1;i <= m;i++)
if(!dfn[i])
tarjan(i);
for(int i = 1;i <= n;i++)
if(bel[i] == bel[i+n])
return void(fout << "No\n");
fout << "Yes\n";
}
abc211
D
BFS
最短路计数板子。
设 \(f_i\) 为从起点到 \(i\) 的最短路计数,跑 01bfs 即可。
const int N = 2e5+10;
const int M = 1e9+7;
int f[N],vis[N],dis[N];
std::vector<int> E[N];
void bfs()
{
memset(dis,0x3f,sizeof(dis));
std::queue<int> q;
q.push(1);vis[1] = 1;
dis[1] = 0,f[1] = 1;
while(!q.empty())
{
int u = q.front();
q.pop();
for(int v : E[u])
if(dis[v] >= dis[u] + 1)
{
if(!vis[v])
q.push(v),vis[v] = 1;
dis[v] = dis[u]+1;
f[v] = (f[v] + f[u]) % M;
}
}
}
inline void solve()
{
int n,m; fin >> n >> m;
for(int i = 1;i <= m;i++)
{
int u,v;fin >> u >> v;
E[u].emplace_back(v);
E[v].emplace_back(u);
}
bfs();
fout << f[n];
}
E
暴搜
看到数据范围,每次根据已经染过色的格子进行扩展即可,过程可以使用暴搜 + 回溯。
#define int long long
#define pii std::pair<int,int>
const int N = 10;
int ans,n;
char a[N][N];
const int dx[4] = {0,0,1,-1};
const int dy[4] = {-1,1,0,0};
void dfs(int cnt)
{
if(cnt == 0) {ans++;return;}
std::vector<pii> vis;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if(a[i][j] == '.')
{
bool f = 0;
for(int k = 0;k < 4;k++)
{
int x = i + dx[k] , y = j + dy[k];
if(x >= 1 && y <= n && x <= n && y >= 1 && a[x][y] == 'r')
f = 1;
}
if(f)
{
a[i][j] = 'r';
dfs(cnt-1);
a[i][j] = '#';
vis.push_back({i,j});
}
}
for(auto it : vis)
a[it.first][it.second] = '.';
}
inline void solve()
{
int k; fin >> n >> k;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
fin >> a[i][j];
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if(a[i][j] == '.')
{
a[i][j] = 'r';
dfs(k-1);
a[i][j] = '#';
}
fout << ans ;
}
F
扫描线
扫描线板子。
线段树 / 树状数组维护一维,排序后维护一维。由于我们只需要区间加和单点查询,所以我们选用树状数组来维护。
对于每一个单独的图形,发现每一次转角后切割线线的权值就会反转,模拟即可。
#define int long long
#define pii std::pair<int,int>
#define fi first
#define se second
const int N = 4e5+10;
int n,tr[N],cnt;
void add(int x,int k){for(;x<=N;x+=x&-x)tr[x]+=k;}
void modify(int x,int y,int k){add(x,k);add(y+1,-k);}
int query(int x){int res=0;for(;x;x-=x&-x)res+=tr[x];return res;}
struct node{int op,x,y1,y2,w;}a[N];
inline void solve()
{
fin >> n;
int cnt = 0;
for(int i = 1;i <= n;i++)
{
int m; fin >> m;
std::vector<pii> t(2*m+1);
for(int j = 1;j <= m;j++)
{
fin >> t[j].fi >> t[j].se;
t[j].fi++; t[j].se++;
t[j+m] = t[j];
}
pii ans = {1e18,1e18};
int k;
for(int j = 1;j <= m;j++)
if(t[j] < ans)
ans = t[j] ,k = j;
std::vector<int> tt(2*m+1);
int w = 1;
for(int j = k;j <= k+m-1;j++,w = -w)
if(j > m) tt[j-m] = w;
else tt[j] = w;
for(int j = 1;j <= m;j += 2)
a[++cnt] = {0,t[j].fi, t[j].se, t[j+1].se, (t[j].se < t[j+1].se) ? tt[j] : tt[j+1]};
}
for(int i = 1;i <= cnt;i++)
if(a[i].y1 > a[i].y2)
std::swap(a[i].y1 , a[i].y2);
int q; fin >> q;
for(int i = 1;i <= q;i++)
{
int x,y;fin >> x >> y;
a[++cnt] = {1,x+1,y+1,i,0};
}
std::sort(a+1,a+cnt+1,[](node x,node y){
if(x.x == y.x) return x.op < y.op;
return x.x < y.x;
});
std::vector<int> res(q+1);
for(int i = 1;i <= cnt;i++)
{
if(a[i].op) res[a[i].y2] = query(a[i].y1);
else modify(a[i].y1,a[i].y2-1,a[i].w);
}
for(int i = 1;i <= q;i++) fout << res[i] << '\n';
}
abc212
D
模拟
显然,直接对集合内的元素统一做更改比较困难,所以我们考虑更改加入元素的值,每次对集合内元素做整体加法时,相当于对以后要加入的元素做减法,最后询问的时候把累计更改的值加上即可,整体可以使用堆维护。
#define int long long
inline void solve()
{
std::priority_queue<int> q;
int Q; fin >> Q;
int sum = 0;
int mn = 1e18;
while(Q--)
{
int op,x ; fin >> op;
if(op == 1)
{
fin >> x;
q.push(-(x-sum));
}
if(op == 2)
{
fin >> x;
sum += x;
}
if(op == 3)
{
fout << -q.top() + sum << '\n';
q.pop();
}
}
}
E
计数DP + 前缀和
很简单的计数题,推完式子后发现直接暴力转移会 T ,遂使用前缀和优化。
把上次枚举的和记录下来,供本次枚举使用,因为有限制边,所以把限制边的 dp 值减去即可。
#define int long long
const int M = 998244353;
const int N = 5e3+10;
std::vector<int> E[N];
int f[N][N],sum[N],b[N][N];
inline void solve()
{
int n,m,k; fin >> n >> m >> k;
for(int i = 1;i <= m;i++)
{
int u,v; fin >> u >> v;
E[u].emplace_back(v);
E[v].emplace_back(u);
b[u][v] = b[v][u] = 1;
}
f[0][1] = 1;
sum[0] = 1;
for(int i = 1;i <= k;i++)
for(int j = 1;j <= n;j++)
{
f[i][j] = (sum[i-1] - f[i-1][j] + M) % M;
for(int v : E[j])
f[i][j] = (f[i][j] - f[i-1][v] + M) % M;
sum[i] = (sum[i] + f[i][j]) % M;
}
fout << f[k][1];
}
F
倍增
坐完每一辆车都必定会使用离你最近到达的车离开,也就是对于每一辆车都有相应的后继。
发现是一棵树而且是离线查询的,考虑倍增。
#define int long long
#define pii std::pair<int,int>
#define fi first
#define se second
const int N = 2e5+10;
const int M = 30;
struct node{int a,b,s,t;}a[N];
int n,m,q,f[N][M];
std::set<pii> st[N];
int get(int x,int y)
{
auto it = st[x].lower_bound({y,0});
if(it != st[x].end()) return (*it).se;
return -1;
}
inline void solve()
{
int n,m,q; fin >> n >> m >> q;
memset(f,-1,sizeof(f));
for(int i = 1;i <= m;i++)
{
fin >> a[i].a >> a[i].b >> a[i].s >> a[i].t;
st[a[i].a].insert({a[i].s,i});
}
for(int i = 1;i <= m;i++)
f[i][0] = get(a[i].b,a[i].t);
for(int j = 1;j <= 20;j++)
for(int i = 1;i <= m;i++)
if(f[i][j-1] != -1)
f[i][j] = f[f[i][j-1]][j-1];
while(q--)
{
int x,y,z; fin >> x >> y >> z;
int tmp = get(y,x);
if(tmp == -1)
{
fout << y << '\n';
continue;
}
for(int i = 20;i >= 0;i--)
if(f[tmp][i] != -1 && a[f[tmp][i]].s < z) tmp = f[tmp][i];
if(a[tmp].s >= z) fout << a[tmp].a << '\n';
else if(a[tmp].t < z) fout << a[tmp].b << '\n';
else fout << a[tmp].a << " " << a[tmp].b << '\n';
}
}
abc213
D
DFS
输出欧拉序即可,要先遍历小的编号,所以让邻接表按照点的编号大小来排序。
#define int long long
const int N = 2e5+10;
std::vector<int> E[N];
std::vector<int> ans;
void dfs(int u,int fa)
{
ans.emplace_back(u);
for(int v : E[u])
{
if(v == fa) continue;
dfs(v,u);
ans.emplace_back(u);
}
}
inline void solve()
{
int n; fin >> n;
for(int i = 1;i < n;i++)
{
int u,v; fin >> u >> v;
E[u].emplace_back(v);
E[v].emplace_back(u);
}
for(int i = 1;i <= n;i++)
std::sort(E[i].begin(),E[i].end());
dfs(1,0);
for(int x : ans) fout << x << ' ';
}
E
BFS
使用 BFS 直接转移即可。
#define int long long
#define pii std::pair<int,int>
#define fi first
#define se second
const int dx[] = {0,0,-1,1};
const int dy[] = {1,-1,0,0};
const int N = 1e3+10;
char a[N][N];
int dis[N][N];
inline void solve()
{
int n,m; fin >> n >> m;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
fin >> a[i][j];
memset(dis,0x3f,sizeof(dis));
std::deque<pii> q;
q.push_front({1,1});
dis[1][1] = 0;
while(!q.empty())
{
auto [x,y] = q.front();
q.pop_front();
for(int k = 0;k < 4;k++)
{
int nx = dx[k] + x, ny = dy[k] + y;
if(nx < 1 ||nx > n ||ny < 1 || ny > m||a[nx][ny] == '#'|| dis[nx][ny] <= dis[x][y]) continue;
q.push_front({nx,ny});
dis[nx][ny] = dis[x][y];
}
for(int i = -2;i <= 2;i++)
for(int j = -2;j <= 2;j++)
{
if(abs(i) + abs(j) == 4) continue;
int nx = i + x , ny = j + y;
if(nx < 1 ||nx > n ||ny < 1 || ny > m|| dis[nx][ny] <= dis[x][y] + 1) continue;
q.push_back({nx,ny});
dis[nx][ny] = dis[x][y] + 1;
}
}
fout << dis[n][m] ;
}
abc214
D
并查集
考虑每一条边的贡献,发现只有路径上不存在其它边的边权比当前边大的情况才能产生贡献,将边按照边权从小到大排序,然后使用并查集进行连边,只需要知道这条边左右两边当前连通块的大小就能知道这条边的贡献。
#define int long long
const int N = 2e5+10;
int fa[N],n,siz[N];
int find(int x)
{
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void init(){for(int i = 1;i <= n;i++) fa[i]=i,siz[i]=1;}
struct node{int u,v,w;}e[N];
inline void solve()
{
fin >> n; init();
int tot = 0;
for(int i = 1;i < n;i++)
{
int u,v,w; fin >> u >> v >> w;
e[i] = {u,v,w};
}
std::sort(e+1,e+n,[](node a,node b){
return a.w < b.w;
});
int ans = 0;
for(int i = 1;i < n;i++)
{
auto [u,v,w] = e[i];
v = find(v), u = find(u);
ans += siz[v] * siz[u] * w;
fa[v] = fa[u];siz[u] += siz[v];siz[v] = 0;
}
fout << ans << '\n';
}
E
贪心
贪心的,我们每次肯定优先选用左端点取球,若有多个区间的左端点重叠,则我们按照右端点的大小从左端点开始从左往右依次取球。
#define int long long
#define l first
#define r second
#define pii std::pair<int,int>
inline void solve()
{
int n; fin >> n;
std::vector<pii> a(n+2);
a[n+1].l = 2e9,a[n+1].r = 2e9;
for(int i = 1;i <= n;i++) fin >> a[i].l >> a[i].r;
std::sort(a.begin()+1,a.end());
std::priority_queue<int> q;
for(int i = 1;i <= n;)
{
q.push(-a[i].r);
int j = i+1 , s = a[i].l;
while(a[j].l == a[i].l)
{
q.push(-a[j].r);
j++;
}
while(s < a[j].l && !q.empty())
{
int u = -q.top();q.pop();
if(s > u) {return void(fout<<"No\n");}
s++;
}
i = j;
}
fout << "Yes\n";
}
F
动态规划
设计状态 \(f(i,j)\) 为对于前 \(i\) 个字符,以字母 \(j\) 结尾的方案数。对于对应的结尾字符,我们可以从所有的 \(f(i-2,k)\{k \in 'a' -'z'\}\) 状态转移而来,反之只能从上一个位置的对应字符转移而来。
#define int long long
const int N = 2e5+10 , M = 1e9+7;
int f[N][100],sum[N];
inline void solve()
{
std::string s;fin>>s;int n = s.size();
s = ' ' + s;
f[1][s[1]-'a'] = 1;
for(int i = 2;i <= n;i++)
for(int j = 0;j < 26;j++)
{
if(s[i] == (char)(j + 'a'))
{
for(int k = 0;k < 26;k++) f[i][j] += f[i-2][k];
f[i][j]++;
}
else f[i][j] = f[i-1][j];
f[i][j] %= M;
}
int ans = 0;
for(int j = 0;j < 26;j++) ans = (ans + f[n][j]) % M;
fout << ans << '\n';
}

浙公网安备 33010602011771号