第49周
T2
给定一个长度为 n n n 的整数序列 a 1 , a 2 , … , a n a1,a2,…,an a1,a2,…,an。
请你找到一个该序列的子序列,要求:
1,该子序列的所有元素之和必须是奇数。
2,在满足条件 1 的前提下,该子序列的所有元素之和应尽可能大。
输出你找到的满足条件的子序列的所有元素之和。
- 建模贪心:
1,理论最大值:所有正元素的和
2,理论实际差值:理论最优可能是偶数
3,消除差异的方法:加一个负奇数或者减一个正奇数,自然绝对值要尽可能小
4,思想:理论最值微扰到实际,理论基础:微扰证明贪心
int main()
{
int delta = INF;
int sum = 0;
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> x;
if(x > 0) sum += x;
if (x % 2)delta = min(delta,abs(x));
}
if(sum % 2) cout << sum;
else cout << sum - delta;
}
- 建模DP
1, f [ i ] [ 1 ] f[i][1] f[i][1] 以 i i i 为结尾的和为奇数的最大值, f [ i ] [ 0 ] f[i][0] f[i][0] 以 i i i 为结尾的和为偶数的最大值
2,从实际意义出发初始化:一个数都不选的和为0,0为偶数,因此 f [ 0 ] [ 0 ] f[0][0] f[0][0]是合法为0, f [ 0 ] [ 1 ] f[0][1] f[0][1]是不合法不能转移,需要最大值,所以初始化负无穷
3,思维卡点:应该开成两维分析,而不是简单分析为单维
int main()
{
///0是偶数,1奇数
f[0][1]= -INF;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ )
{
if(a[i]%2)
{
f[i][0] = max(f[i-1][0],f[i-1][1]+a[i]);
f[i][1] = max(f[i-1][1],f[i-1][0]+a[i]);
}
else
{
f[i][0] = max(f[i-1][0],f[i-1][0]+a[i]);
f[i][1] = max(f[i-1][1],f[i-1][1]+a[i]);
}
}
cout << f[n][1];
}
T3
给定一个 n 个点 m 条边的无向无权图。点的编号为 1∼n,图中不含重边和自环。
现在,请你给图中的每个点进行赋值,要求:
1,每个点的权值只能是 1 或 2 或 3。
2,对于图中的每一条边,其两端点的权值之和都必须是奇数。
请问,共有多少种不同的赋值方法(mod 998244353)
- 建模:染色
- 1,题目要求一条边的两点不同色,很像二分图染色
展开:
ii,染色矛盾判断无解,符合二分图染色步骤
ii,染色成功可分步乘法,默认黑点为奇数,可有1,3两种选法
ii,对称思考:黑白交换,染色结构不变,也是解法(1,3选法) - 2,如果图不联通,需要分步乘法
- 3,如果图是单点,是3种选法(1,2,3)
bool bfs(int st)
{
queue<int>q;
q.push(st);
d[st] = 2;
while (q.size())
{
int u = q.front();
q.pop();
mrep(i,u)
{
int v =e[i];
if(d[v]&&d[v]!=d[u]^1)return 0;
if(d[v])continue;
d[v] = d[u]^1;
ans[d[v]]++;
q.push(v);
}
}
ans[2]++;
return 1;
}
int work()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) h[i] = -1;
for (int i = 1; i <= n; i ++ ) du[i]=d[i]=0;
idx = 0; ans[2] = 0; ans[3] = 0;
LL res = 1;
for (int i = 1; i <= m; i ++ )
{
int a,b;
cin >> a >> b;
add(a, b); add(b, a);
du[a]++; du[b]++;
}
for (int i = 1; i <= n; i ++ )
{
if(d[i]) continue;
if(du[i]==0)
{
res = (res*3)%mod;
continue;
}
else if(!bfs(i)) return 0;
res = res*(ksm(2,ans[2],mod)+(LL)ksm(2,ans[3],mod))%mod;
ans[2] = 0; ans[3] = 0;
}
return res;
}
错误点分析:
1,不能直接累计所有白点和黑点个数再算,不同联通块的处理是分步的过程(胡乱结合律)
2,多组数据,直接memset会T,这是一个坑
3,度数为0和总点数的个数直接互不干扰,不要重复判断:if(n>1&&du[i]==0)res = (res*3)%mod;
(迷惑行为)
第28周
T3
有 n n n 个小朋友,编号 1 ∼ n 1∼n 1∼n。
每个小朋友都拿着一个号码牌,初始时,每个小朋友拿的号码牌上的号码都等于其编号。
每个小朋友都有一个幸运数字,第 i i i 个小朋友的幸运数字为 d i d_i di。
对于第 i i i 个小朋友,他可以向第 j j j 个小朋友发起交换号码牌的请求,当且仅当 ∣ i − j ∣ = d i |i−j|=d_i ∣i−j∣=di 成立。
注意,请求一旦发出,对方无法拒绝,只能立刻进行交换。
每个小朋友都可以在任意时刻发起任意多次交换请求。
给定一个 1 ∼ n 1∼n 1∼n 的排列 a 1 , a 2 , … , a n a1,a2,…,an a1,a2,…,an。
请问,通过小朋友相互之间交换号码牌,能否使得第 i 个小朋友拿的号码牌上的号码恰好为 a i a_i ai,对 i ∈ [ 1 , n ] i∈[1,n] i∈[1,n] 均成立。
-
输入格式:第一行包含整数 n,第二行包含 n 个整数 a 1 , a 2 , … , a n a1,a2,…,an a1,a2,…,an,第三行包含 n 个整数 d 1 , d 2 , … , d n d1,d2,…,dn d1,d2,…,dn。
-
输出格式:如果能做到,输出
YES
,否则输出NO
。 -
数据范围:所有测试点满足 1≤n≤100,1≤di≤n,保证 a1∼an 是一个 1∼n 的排列。
- 建模:联通块(并查集)
交换方式唯一,说白了就是一个可以互相交换的小团体翻来覆去的换,可以交换达到的任意两点绝对可达 - 考虑以联通块编号为数组下标,构建一个联通块内可达点集
- 同样以下边所在联通块编号为下标,标注一个题目要求达的点集,比较集合相等即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+9;
vector <int> A[N],B[N];
int fa[N], a[N] , d[N];
int find (int x)
{
if(fa[x]==x) return x;
else return fa[x]= find(fa[x]);
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d",&a[i]);
for (int i = 1; i <= n; i ++ ) scanf("%d",&d[i]);
for (int i = 1; i <= n; i ++ ) fa[i] =i;
for (int i = 1; i <= n; i ++ )
{
if(i+d[i]<=n) fa[find (i+d[i])] =find (i);
if(i-d[i]>=1) fa[find (i-d[i])] =find (i);
}
for (int i = 1; i <= n; i ++ )
{
A[find (i)].push_back (i);
B[find (i)].push_back (a[i]);
}
for (int i = 1; i <=n; i ++ )
{
sort (B[i].begin(),B[i].end());
if(A[i]!=B[i])
{
puts ("NO");
return 0;
}
}
puts("YES");
return 0;
}