题目列表
AcWing 4413. 组队
题目描述
给定 n 个整数 y1,y2,…,yn,及一个整数 k。
先求出有多少个 yi 满足 yi+k≤5,然后输出满足要求的 yi 的个数除以 3 下取整的值。
输入格式 第一行包含两个整数 n 和 k。
第二行包含 n 个整数 y1,y2,…,yn。
输出格式 一个整数,表示答案。
数据范围 前 4 个测试点满足 1≤n≤6。 所有测试点满足 1≤n≤2000,1≤k≤5,0≤yi≤5。
输入样例1: 5 2 0 4 5 1 0 输出样例1: 1 输入样例2: 6 4 0 1 2 3 4 5 输出样例2: 0 输入样例3: 6 5 0 0 0 0 0 0 输出样例3: 2
分析
简单的签到题,遍历一下数组,统计下符合条件的元素个数即可。
代码
#include <iostream>
using namespace std;
int main(){
int n,k,y;
cin>>n>>k;
k = 5 - k;
int cnt = 0;
for(int i = 0;i < n;i++) {
cin>>y;
if(y <= k) cnt++;
}
cout<<cnt / 3<<endl;
return 0;
}
AcWing 4414. 子序列
题目描述
给定一个长度为 n 的整数序列 a1,a2,…,an。
请你找到一个该序列的子序列,要求:
该子序列的所有元素之和必须是奇数。 在满足条件 1 的前提下,该子序列的所有元素之和应尽可能大。 输出你找到的满足条件的子序列的所有元素之和。
保证至少存在一个满足条件的子序列。
注意,子序列不一定连续。
输入格式 第一行包含一个整数 n。
第二行包含 n 个整数 a1,a2,…,an。
输出格式 输出一个整数,表示满足条件的子序列的所有元素之和。
数据范围 前 6 个测试点满足 1≤n≤5。 所有测试点满足 1≤n≤105,?104≤ai≤104。
输入样例1: 4 -2 2 -3 1 输出样例1: 3 输入样例2: 3 2 -5 -3 输出样例2: -1
分析
题目意思是在数组里面选若干个元素,使得这些因素的和为奇数的前提下和最大,输出最大的奇数和。 一种思路是直接用数学知识加上贪心思想求解。我们知道,对一个数加上偶数不改变其奇偶性,加上奇数改变其奇偶性,所以对于数组里面的偶数,只要是正数都可以加上,只要是负数都可以舍弃;对于数组里面的奇数,如果正奇数的数量是奇数,都加上得到的和也是奇数,如果正奇数的数量是偶数,为了使和为奇数并且最大,要么去掉一个最小的正奇数,要么加上一个最大的负奇数,取这两种情况下和最大的方案。所以这种思路只需要一遍遍历,统计下最小正奇数和最大负奇数即可。时间复杂度是O(n)。 另一种思路是DP,本题还是比较符合状态机模型的。DP的经典应用有求解最大连续子序列和,本题求最大子序列奇数和也是可以用DP解决的。f[i][0]表示前i个元素里和是奇数的子序列的最大和,f[i][1]表示前i个元素里和是偶数的子序列的最大和。那么根据第i个元素的奇偶性,就可以进行状态划分和状态转移。 如果a[i]是奇数,加上后会改变和的奇偶性。所以f[i][0]可以由三种状态转移而来,要么继承f[i-1][0]的值,也就是不选第i个元素,要么放弃前i-1个元素,只取a[i],要么由f[i][1]加上a[i]得到,取三者的最大值,也就是f[i][0] = max(a[i],f[i-1][0], f[i-1][1]+a[i]);f[i][1]只能由两种状态转移而来,要么是继承f[i-1][1]的值,放弃第i个元素,要么由f[i-1][0]加上a[i]得到,即f[i][1] = max(f[i-1][1], f[i-1][0]+a[i])。 如果a[i]是偶数,加上后不改变和的奇偶性。所以f[i][0]可以由两种状态转移而来,要么继承f[i-1][0]的值,也就是不选第i个元素,要么由f[i][0]加上a[i]得到,取二者的最大值,也就是f[i][0] = max(f[i-1][0], f[i-1][0]+a[i]);f[i][1]可以由三种状态转移而来,要么是继承f[i-1][1]的值,放弃第i个元素,要么放弃前i-1个元素,只取a[i],要么由f[i-1][1]加上a[i]得到,即f[i][1] = max(a[i], f[i-1][1], f[i-1][1]+a[i])。 可见DP的解法如果不提前梳理下状态的划分,是很容易出错的。具体代码实现就不需要这么复杂的分类讨论了。我们可以令s = a[i] & 1,如果a[i]是奇数,s = 1;如果a[i]是偶数,则s = 0。不论奇偶,f[i-1][0]和f[i-1][1]都分别是f[i][0]和f[i][1]的候选解之一。而a[i]也是f[i][1-s]的候选解之一,比如s = 1,1 - s = 0,f[i][0]表示前i个元素里的最大奇数和,a[i]自然就是候选解之一了。f[i][0] 的最后一个候选解是 f[i-1][s] + a[i],f[i][1] 的最后一个候选解是 f[i-1][t] + a[i],t = 1 - s。用字母表示状态可以简化代码,但是更容易出错。
代码
方法一:贪心
#include <cstdio>
#include <algorithm>
using namespace std;
int main(){
int n,a;
scanf("%d",&n);
int res = 0;
int jp = 10000,jn = -10000;
for(int i = 0;i < n;i++) {
scanf("%d",&a);
if(a > 0) {
res += a;
if(a & 1 && a < jp) jp = a;
}
else{
if(a & 1 && a > jn) jn = a;
}
}
if((res & 1) == 0) res = max(res - jp,res + jn);
printf("%d\n",res);
return 0;
}
方法二:动态规划
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100005;
int a[N],f[N][2];
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
f[0][0] = f[0][1] = -1e9;
for(int i = 1;i <= n;i++) {
int s = a[i] & 1,t = 1 - s;
f[i][0] = f[i-1][0],f[i][1] = f[i-1][1];
f[i][t] = max(f[i][t], a[i]);//之前的太小则只保留当前元素
f[i][0] = max(f[i][0], f[i-1][s] + a[i]);
f[i][1] = max(f[i][1],f[i-1][t] + a[i]);
}
printf("%d\n",f[n][0]);
return 0;
}
AcWing 4415. 点的赋值
题目描述
给定一个 n 个点 m 条边的无向无权图。
点的编号为 1~n。
图中不含重边和自环。
现在,请你给图中的每个点进行赋值,要求:
每个点的权值只能是 1 或 2 或 3。 对于图中的每一条边,其两端点的权值之和都必须是奇数。 请问,共有多少种不同的赋值方法。
由于结果可能很大,你只需要输出对 998244353 取模后的结果。
输入格式 第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含两个整数 n,m。
接下来 m 行,每行包含两个整数 u,v,表示点 u 和点 v 之间存在一条边。
输出格式 一个整数,表示不同赋值方法的数量对 998244353 取模后的结果。
数据范围 前三个测试点满足 1≤T≤6,∑i=1Tm≤50。 所有测试点满足 1≤T≤3×105,1≤n≤3×105,0≤m≤3×105,1≤u,v≤n,∑i=1Tn≤3×105,∑i=1Tm≤3×105。
输入样例: 2 2 1 1 2 4 6 1 2 1 3 1 4 2 3 2 4 3 4 输出样例: 4 0
分析
本题考察二分图,二分图的判定可以参考之前的题解AcWing 860 染色法判定二分图。简单的说就是遍历下图,顺便给图上的节点染个色,相邻的节点不能染相同的颜色,比如节点u染成颜色1,则u相邻的节点就需要全部染成颜色2。如果u的相邻节点v没有被染色,就判断下能否递归的给v染色,如果没有合法的方案,该图就不是二分图;如果v已经被染色,就判断下染成的颜色和u是否相同,相同则说明该方案不合法。 虽然本题点的权重有1, 2,3三种情况,但是限制条件只有相邻节点的和是奇数这一个条件,也就是说如果一个节点的权重是奇数,那么其相邻节点的权重只能是偶数,我们只需要调用下二分图的算法,统计下每个连通块里面的奇数权重节点的个数cnt1和偶数权重节点的个数cnt2即可。奇数权重节点可以染成1和3,一共有2cnt1种染色方法,当然如果将之前合法的染色方案全部反过来必然也就是一种合法的方案,比如有1到2, 2到3的图,1,3染成奇数,2染成偶数合法,则1,3染成偶数,2染成奇数也必然合法,所以反过来的方案一共有2cnt2种方案,一共就是2cnt1 * 2cnt2种方案。注意偶数节点的个数不能用图节点的个数n减去奇数节点的个数,因为一张图可能有多个连通块,每个连通块奇数和偶数节点的和不一定是n。另外注意本题的用例树挺多,memset的时候只需要重置前n + 1个元素,不需要重置所有的元素,否则会TLE。染色法判断二分图的算法比较简单,具体实现参考代码。 还有点需要强调的是如果用快速幂求2的整数次幂,快速幂的算法易错点很多。比如211 = 28 * 22 * 21。11 = 1 + 2 + 8,每次对指数n右移一位时权重需要平方而不是翻倍,举例来说就是28是24的平方。另外,暴力做法每次乘2再取模基本不会溢出,而快速幂在对权重平方时很可能溢出,乘上权重也可能溢出,所以使用long long存储权重是有必要的。
代码
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 300005,M = 2 * N,mod = 998244353;
typedef long long ll;
int h[N],w[N],e[M],ne[M],idx;
int cnt1,cnt2;
void add(int a,int b) {
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
ll pow2(int n) {
ll res = 1;
ll k = 2;
while(n){
if(n & 1) res = (res * k) % mod;
n >>= 1;
k = k * k % mod;//不使用long long存储就会溢出
}
return res;
}
bool dfs(int u,int c) {
w[u] = c;
if(c == 1) cnt1++;
else cnt2++;
for(int i = h[u];~i;i = ne[i]) {
int j = e[i];
if(!w[j] && !dfs(j,3 - c)) return false;
if(w[j] && (w[j] == c)) return false;
}
return true;
}
int main(){
int T,n,m,a,b;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
memset(h,-1,4*(n+1));
memset(w,0,4*(n+1));
for(int i = 0;i < m;i++) {
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
ll res = 1;
for(int i = 1;i <= n;i++) {
if(!w[i]) {
cnt1 = cnt2 = 0;
if(dfs(i,1)) res = (res * (pow2(cnt1) + pow2(cnt2)) % mod);
else {
res = 0;
break;
}
}
}
printf("%d\n",res);
}
return 0;
}
|