2024“钉耙编程”中国大学生算法设计超级联赛(6)
写在前面
补提地址:https://acm.hdu.edu.cn/listproblem.php?vol=65,7494~7504
以下按个人向难度排序。
前期超级顺利的一场,双人双开环节仅持续 10min 即结束,然而二段双人双开环节一直到最后都没结束呃呃后面三题都会都没调出来呃呃
1003
签到。
直接模拟每次铺的铁路位置和当前朝向即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define pr std::pair
#define mp std::make_pair
#define LL long long
int ex[4] = {-1, 0, 1, 0};
int ey[4] = {0, -1, 0, 1};
//=============================================================
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n; std::cin >> n;
std::string s; std::cin >> s;
std::map <pr<int,int>, bool> vis;
int x = 0, y = 0, d = 0;
int ans = 1;
for (auto ch: s) {
x = x + ex[d], y = y + ey[d];
if (vis[mp(x, y)] == 1) ans = -1;
vis[mp(x, y)] = 1;
if (ch == 'L') {
d = (d + 1 + 4) % 4;
} else if (ch == 'R') {
d = (d - 1 + 4) % 4;
} else {
continue;
}
}
if ((x != 0 || y != 0 || d != 0) && ans != -1) ans = 0;
std::cout << ans << "\n";
}
return 0;
}
/*
1
4
RRRR
1
4
RRRS
*/
1004
模拟。
不在任何上课时间犯困或睡大觉,等价于:
- 所有睡大觉时间和上课时间没有交集;
- 所有上课区间均被某个个清醒时间完全包含。
于是枚举所有睡大觉时间判断是否有交集,再枚举所有清醒时间并大力枚举标记区间内的上课区间,最后检查是否所有上课区间均被标记即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define int long long
const int kN = 5e5 + 10;
const int kInf = 2e9;
//=============================================================
int n, m;
int tag[kN], b[kN], e[kN];
//=============================================================
//=============================================================
signed main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) std::cin >> b[i] >> e[i];
for (int i = 1; i <= n; ++ i) tag[i] = 0;
b[0] = -kInf, b[n + 1] = kInf, e[n + 1] = kInf;
int no = 0;
for (int i = 1, p = 1; i <= m; ++ i) {
int s, t; std::cin >> s >> t;
int lst = std::lower_bound(e, e + n + 1, s) - e;
if (b[lst] >= s) -- lst;
if (b[lst + 1] < t) no = 1;
int tt = t + 2ll * (t - s);
while (p <= n && b[p] < t) ++ p;
while (p <= n && e[p] <= tt) tag[p] = 1, ++ p;
}
for (int i = 1; i <= n; ++ i) if (!tag[i]) no = 1;
std::cout << (no ? "No" : "Yes") << "\n";
}
return 0;
}
1001
特判,结论。
dztlb 大神秒了,我看都没看。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n,head[N],du[N],tot;
struct node{
int to,nxt;
}e[N<<1];
void add(int u,int v){
e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot;
}
int cnt[N];
signed main(){
cin>>t;
while(t--){
cin>>n;
tot=0;
cnt[0]=0;
int num=n-1;
for(int i=1;i<=n;++i){
head[i]=0,du[i]=0;
cnt[i]=0;
}
for(int i=1,u,v;i<n;++i){
cin>>u>>v;
add(u,v),add(v,u);
du[u]++,du[v]++;
}
for(int i=1;i<=n;++i){
++cnt[du[i]];
}
bool fl=0;
for(int i=1;i<=n;++i){
--cnt[du[i]];
int tmp=num;
for(int j=head[i];j;j=e[j].nxt){
int v=e[j].to;
tmp--;
--cnt[du[v]];
++cnt[du[v]-1];
}
if(n-1-tmp!=2){
// puts("No");
}else{
int tt=0;
tt+=cnt[1];
tt+=cnt[0];
if(cnt[0]==2){
fl=1;
}
if(cnt[0]==1){
if(n-1-tt<=1){
fl=1;
}
}
if(cnt[0]==0){
if(n-1-tt<=1) fl=1;
else if(n-1-tt<=2){
bool hh=0;
// cout<<i<<endl;
for(int j=head[i];j;j=e[j].nxt){
int v=e[j].to;
if(du[v]==2){
for(int k=head[v];k;k=e[k].nxt){
int vv=e[k].to;
if(vv==i) continue;
if(du[vv]==1){
hh=1;
}
}
}
}
if(!hh){ fl=1;
// cout<<"!!!!\n";
}
}
}
}
++cnt[du[i]];
for(int j=head[i];j;j=e[j].nxt){
int v=e[j].to;
++cnt[du[v]];
--cnt[du[v]-1];
}
}
if(fl){
puts("Yes");
}else{
puts("No");
}
}
return 0;
}
/*
3
3
1 2
2 3
4
1 2
1 3
1 4
9
1 2
2 3
1 4
4 5
5 6
5 7
5 8
8 9
*/
1005
状压 DP。
三进制状压 DP,比较板没啥可说的。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100005;
int T,n,k,mod;
char s[505][15];
int a[2][60005];
bool fl[60006];
int now[60005],tot;
int poww[12];
int b[60006],cnt;
int p;
signed main(){
poww[0]=1;
for(int i=1;i<=10;++i){
poww[i]=poww[i-1]*3;
}
cin>>T;
while(T--){
tot=0;
cin>>n>>k>>mod;
for(int i=0;i<60000;++i){
a[0][i]=0,a[1][i]=0;
b[i]=0;
fl[i]=0;
}
p=0;
for(int i=1;i<=n;++i){
scanf("%s",s[i]+1);
}
a[p][0]=1;
fl[0]=1;
++tot;
now[tot]=0;
for(int i=1;i<=n;++i){
cnt=0;
for(int o=1;o<=tot;++o){
int pos=now[o];
a[p^1][pos]=a[p][pos];
}
for(int o=1;o<=tot;++o){
int pos=now[o];
int pre=pos;
for(int j=1;j<=k;++j){
int c=(pos%poww[j])/poww[j-1];
if(s[i][k-j+1]=='+'){
//2-0 0-1 1-2
pos-=c*poww[j-1];
++c;
c%=3;
pos+=c*poww[j-1];
}else if(s[i][k-j+1]=='-'){
//0-2 1-0 2-1
pos-=c*poww[j-1];
--c;
if(c==-1) c+=3;
pos+=c*poww[j-1];
}
}
if(fl[pos]==0){
++cnt;
b[cnt]=pos;
fl[pos]=1;
}
a[p^1][pos]+=a[p][pre]; a[p^1][pos]%=mod;
}
for(int o=1;o<=cnt;++o) now[++tot]=b[o];
p^=1;
}
sort(now+1,now+1+tot);
for(int i=1;i<=tot;++i){
for(int j=k;j>=1;--j){
int c=(now[i]%poww[j])/poww[j-1];
if(c==0) cout<<'A';
if(c==1) cout<<'B';
if(c==2) cout<<'C';
}
cout<<' ';
cout<<a[p][now[i]]%mod<<'\n';
}
}
return 0;
}
/*
*/
1007
树形 DP,暴力。
见过这个套路于是想了下就秒了,过得比 1005 还早。
所有点权值均不同,一个显然的想法是考虑对于 \(0\le i\le n-1\),枚举由权值 \(0\sim i\) 构成的连通子图 \(T_i\),计数有多少连通子图 \(\operatorname{cnt}_i\) 包含它们,则这些连通子图的 MEX 至少为 \(i+1\)。显然包含连通子图 \(T_i\) 的所有连通子图一定包含连通子图 \(T_0\sim T_{i - 1}\),则容易发现 MEX 恰好为 \(i+1\) 的连通子图数即 \(\operatorname{cnt}_i - \operatorname{cnt}_{i + 1}\),则答案即为:
于是仅需考虑如何求得 \(\operatorname{cnt}_i\) 即可,这相当于限定了包含了权值为 \(0\sim i\) 的最小的连通子图上的点必选的构成连通子图的方案数。
首先考虑仅限定一个点必选的方案数 \(\operatorname{cnt}_0\),以 0 为根考虑,发现很容易使用树形 DP 解决。
记状态 \(f_{u}\) 表示根节点 \(u\) 可选可不选时使用以 \(u\) 为根的子树构成以 \(u\) 为根的连通子图的方案数,状态 \(g_{u}\) 表示限定根节点 \(u\) 必选时使用以 \(u\) 为根的子树构成以 \(u\) 为根连通子图的方案数。保证连通子图以 \(u\) 为根,则实际上 \(f_{u}\) 仅比 \(g_{u}\) 多出了所有节点均不选择的方案。再对于每个节点考虑子节点的子树的形态,则有显然的转移方程:
记无任何限制时连通子图方案数为 \(\operatorname{cnt}_{-1}\),则有 \(\operatorname{cnt}_{-1} = f_{0}\),\(\operatorname{cnt}_0 = g_{0}\)。
然后考虑如何使用 \(\operatorname{cnt}_{i - 1}\) 求得 \(\operatorname{cnt}_i\)。发现从 \(T_{i - 1}\) 变为 \(T_{i}\) 时,实际上可以看做每次新增了一条从根 0 到 \(i\) 的路径 \(0\rightarrow i\) 必选,于是仅需考虑这条路径上新增的不在 \(T_{i-1}\) 中的节点构成的链 \(i'\rightarrow i\) 上各节点必选的影响。
按照上述 DP 数组的定义容易发现,按照 \(i'\rightarrow i\) 的顺序每次新增一个必选的节点 \(u\) 时,相当于令 \(\operatorname{cnt}:= \operatorname{cnt} \div f_{u} \times g_u\),又有 \(g_{u}=\prod f_{v}\),新增节点的顺序实际上是随意的。于是考虑在枚举 \(i\) 过程中标记所有已经在 \(T_i\) 中的节点,每次更新 \(i\) 时直接从 \(i\) 暴力上跳到第一个被标记的节点,边跳边标记并更新 \(\operatorname{cnt}\) 即可由 \(\operatorname{cnt}_{i - 1}\) 得到 \(\operatorname{cnt}_i\)。
每个节点一定仅会被标记一次,总时间复杂度 \(O(n)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const LL p = 998244353;
//=============================================================
int n, a[kN], pos[kN];
int fa[kN], edgenum, head[kN], v[kN << 1], ne[kN << 1];
bool tag[kN];
LL ans, cnt, f[kN], g[kN];
//=============================================================
void addedge(int u_, int v_) {
v[++ edgenum] = v_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
LL qpow(LL x_, LL y_) {
LL ret = 1;
while (y_) {
if (y_ & 1) ret = ret * x_ % p;
x_ = x_ * x_ % p, y_ >>= 1ll;
}
return ret;
}
void dfs(int u_, int fa_) {
fa[u_] = fa_;
f[u_] = g[u_] = 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
dfs(v_, u_);
g[u_] = g[u_] * f[v_] % p;
}
f[u_] = (g[u_] + 1) % p;
}
void jump(int u_) {
while (u_ && !tag[u_]) {
cnt = cnt * qpow(f[u_], p - 2) % p * g[u_] % p;
tag[u_] = 1;
u_ = fa[u_];
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
edgenum = 0;
for (int i = 1; i <= n; ++ i) head[i] = tag[i] = 0;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
pos[a[i]] = i;
}
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
addedge(u_, v_), addedge(v_, u_);
}
dfs(pos[0], 0);
ans = 0, cnt = f[pos[0]];
for (int i = 0; i < n; ++ i) {
jump(pos[i]);
ans = (ans + cnt) % p;
}
std::cout << ans << "\n";
}
return 0;
}
/*
1
6
0 1 2 3 4 5
1 2
1 3
3 4
3 5
2 6
*/
1011
1002
1008
写在最后
学到了什么:
- 1007:首先考虑简单子问题,并由子问题递推。
然后是日常夹带私货环节: