1223A CME
题意:给定一个数
0
≤
n
≤
1
e
9
0 \le n \le 1e9
0≤n≤1e9,把它分成三个数a,b,c使得a + b = c。问至少要增加多少才能分?
题解:
当n==2时,至少需要2。因为最小的等式是1+1=2。
当n>2分两种情况 ,n为偶数时,令
c
=
n
/
2
,
a
=
⌊
n
/
2
⌋
,
b
=
⌈
n
/
2
⌉
c = n/2, a = \lfloor n / 2 \rfloor,b = \lceil{n / 2} \rceil
c=n/2,a=⌊n/2⌋,b=⌈n/2⌉即可
当n为奇数时,因为n = a+b+c = 2(a+b)是不可能的,所以至少加一,变成偶数即可
#include<bits/stdc++.h>
using namespace std;
int main(){
int q;
cin>>q;
while(q--){
int n,ans = 0;
cin>>n;
if(n == 2) ans = 2;
else ans = n & 1;
cout<<ans<<endl;
}
return 0;
}
1223B Strings Equalization
题意:给定两个长度相同的串s,t,你可以每次对s或者t做一个操作,任取其中两个相邻位置,让其中一个的字符等于另一个。问能不能通过一些操作后,s和t相同
题解:注意t也可以做一些操作,所以只要s和t的整个串有相同的字符,把整个串变成这个字符即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
string s1,s2;
cin>>n;
char c1[30] = {0};
char c2[30] = {0};
while(n--){
memset(c1, 0, sizeof(c1));
memset(c2, 0, sizeof(c2));
bool ans = false;
cin>>s1>>s2;
for(int i = 0;i < s1.length(); ++i)
c1[s1[i] - 'a']++;
for(int i = 0;i < s2.length(); ++i)
c2[s2[i] - 'a']++;
for(int i = 0;i < 30; ++i)
if(c1[i] > 0 && c2[i] > 0) ans = true;
if(ans) puts("YES");
else puts("NO");
}
return 0;
}
1223C Save the Nature
题意:给定n个正整数
q
1
,
.
.
.
,
q
n
q_1,...,q_n
q1,...,qn,以及
x
,
a
x,a
x,a,
y
,
b
y,b
y,b 还有
k
k
k
对于这n个数的一个排列,对每个
a
a
a的倍数的位置
i
i
i,我们能得到值
q
i
∗
x
q_i * x
qi∗x,对于每个
b
b
b倍数的位置
j
j
j,我们能得到值
q
j
∗
y
q_j*y
qj∗y。问在这个n个数里面至少取多少个数能使得到的值的和至少为
k
k
k
题解:二分+贪心。二分取的数字个数。先把数从大到小排序,把大的数优先排在a和b的共同倍数上,然后剩下里面继续把大的放在a和b中较大的位置。贪心取。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2 * 1e5 + 10;
vector<long long> p(maxn);
long long gcd(int a, int b){
if(!b) return a;
return gcd(b, a % b);
}
long long lcm(int a, int b){
return a / gcd(a,b) * b;
}
int main(){
int q, n ,x, a, y, b;
long long k;
cin>>q;
while(q--){
cin>>n;
for(int i = 1;i <= n; ++i) {cin >> p[i]; p[i] /= 100;}
cin >> x >> a >> y >> b;
cin >> k;
long long a_b_lcm = lcm(a, b);
long long x_only, y_only, x_y_c, xy = x + y;
if(x > y){
swap(x,y);
swap(a,b);
}
int left = 1, right = n;
int ans = n + 2;
sort(p.begin() + 1, p.begin() + n + 1);
reverse(p.begin() + 1, p.begin() + n + 1);
p[0] = 0;
for(int i = 1;i <= n; ++i) p[i] += p[i - 1];
while(left <= right){
int mid = (right - left) / 2 + left;
long long cur = 0;
x_y_c = mid / a_b_lcm;
x_only = mid / a - x_y_c;
y_only = mid / b - x_y_c;
cur = xy * p[x_y_c];
cur += y * (p[y_only + x_y_c] - p[x_y_c]);
cur += x * (p[x_only + y_only + x_y_c] - p[y_only + x_y_c]);
if(cur >= k){
ans = min(ans, mid);
right = mid - 1;
}else{
left = mid + 1;
}
}
if(ans > n) ans = -1;
cout<<ans<<endl;
}
return 0;
}
1223D Sequence Sorting
题意:给定一个整数数组,你每次可以做一个操作,就是任取其中一个数,让和这个数相同的所有数都挪到左边或者右边去,问至少做多少次操作能使整个数组是非递减的
题解:dp或贪心。
因为每次只能放到最前或者最后。把相同数的看成一个整体。最大那个数一定是最后一个放在末尾的(也可能一个都没放,如果有就是最后一个)。先不管最后一个,假设我们把前面n - 1个先排好了,n有两种情况,一种是不用动,一种是将其放到最末去。
不用动的情况是,其他数只能往最前放。这时候最小的移动数就是n - 从最大开始往左数的最长递减序列长度。
需要动的情况,其他数可以往前也可以往后放。
所以设dp[i]为前i大的数排序的最小次数dp[i] = min(dp[i - 1] + 1,n - len_i)
另外的做法是贪心。假设第i大不用动(显然至少有一个不用动)
则1到i-1大一定是往左放,i + 1到n大一定往右放。所以我们考虑一边,左边。
现在问题变成,给你一个序列,你有一个操作,只能往左放,问最少多少次能使序列递增。这个显然是求从末尾开始的向左的最长递减数。所以如果i不动的最少移动次数就是n减去 i往左的最长递减和往右最长的第增连续序列长度。
所以对于所有位置的答案就是,n - 最长的递增列长度。
(写的乱七八糟,希望能理解)
贪心做法代码
#include<bits/stdc++.h>
using namespace std;
//保存数值和位置
pair<int,int> a[300010];
bool cmp(pair<int,int> &a, pair<int,int> &b){
if(a.first == b.first) return a.second < b.second;
return a.first < b.first;
}
int main()
{
int q,n;
cin>>q;
while(q--){
cin>>n;
for(int i = 0;i < n; ++i) scanf("%d",&a[i].first), a[i].second = i;
sort(a, a+n, cmp);
int ans = 1, tot_num = 0;
int cont = 0;
int prev = -1;
for(int i = 0;i < n; ++i){
++tot_num;
if(prev < a[i].second){
++cont;
}else{
ans = max(ans, cont);
cont = 1;
}
while(i + 1 < n && a[i + 1].first == a[i].first) ++i;
prev = a[i].second;
}
ans = tot_num - max(ans, cont);
cout<<ans<<endl;
}
return 0;
}
1223E Paint the Tree
题意:给定一棵树n个节点n - 1条边,每条边有一个权值w。给定一个数k。你可以对每个点着k种颜色(颜色无穷种)。每种颜色只能出现至多两次。着色后,每条边能获得一个值,如果两个节点有共同颜色,则值为边权否则为0。将所有边值加起来为树的值。问这个值最大为多少?
题解:树dp。注意到 每个点的颜色,要么和儿子匹配,要么取全新的没有被用过是最优的。每个点和父亲只有一条边,所以我们可以对每个点分两种状态,一种k种颜色都被儿子匹配了,另一种是有颜色未被匹配。
设dp[i][0]表示当前节点k种颜色有剩余时(有未被儿子匹配的颜色),子树的最大值。dp[i][1]表示以i为节点的子树的最大值。dp完后答案就是dp[1][1]。
转态转移:对于每个节点假设它有t个儿子,共有2t个状态值
dp[c1][0] ,…, dp[ct][0]
dp[c1][1],…,dp[ct][1]
dp[i][0]的值就是在第一行中取至多k - 1个位置和对应点的边权,数目设为m,然后第二行取剩余的n - m个位置构成的n个的最大的和。表示的就是当前节点和至多k - 1个儿子节点匹配(有共同颜色),剩下的不匹配。
形式化描述就是,选择一个t个数的二进制
b
1
,
.
.
.
,
b
t
,
b
j
∈
{
0
,
1
}
,
∑
b
j
<
k
b_1,...,b_t,b_j\in \{0,1\},\sum b_j < k
b1,...,bt,bj∈{0,1},∑bj<k
d
p
[
i
]
[
0
]
=
max
b
1
,
.
.
.
,
b
t
∑
j
=
1
t
d
p
[
c
j
]
[
b
j
]
+
w
c
j
∗
b
j
dp[i][0] = \max_{b_1,...,b_t} \sum_{j=1}^t dp[c_j][b_j] + w_{c_j} * b_j
dp[i][0]=b1,...,btmaxj=1∑tdp[cj][bj]+wcj∗bj
怎么求这个最大值呢? 注意到上式等于
d
p
[
i
]
[
0
]
=
∑
j
=
1
t
d
p
[
c
j
]
[
1
]
+
max
b
1
,
.
.
.
,
b
t
(
d
p
[
c
j
]
[
0
]
−
d
p
[
c
j
]
[
1
]
+
w
c
j
)
∗
b
j
dp[i][0] =\sum_{j=1}^t dp[c_j][1] + \max_{b_1,...,b_t} \left(dp[c_j][0] - dp[c_j][1] + w_{c_j}\right) * b_j
dp[i][0]=j=1∑tdp[cj][1]+b1,...,btmax(dp[cj][0]−dp[cj][1]+wcj)∗bj
我们可以取数组c[i] = w + dp[ci][0] - dp[ci][1]
然后求一下所有dp[ci][1]的和记为sum
则最大的值dp[i][0]就等于在c数组中取至多k - 1个数的最大值和,这个通过排序就能得到,将c中k - 1个数中大于0的求和即可。
dp[i][1]就是dp[i][0]再补上一种情况,匹配多一个,这时候相当于加上c中第 k k k个(如果大于0)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5*1e5+2;
vector<pair<int,int> > edge[maxn];
long long dp[maxn][2];
void dfs(int u,int fa, const int &k){
vector<long long> weights;
long long sum = 0;
for(int i = 0;i < edge[u].size(); ++i){
pair<int,int> e = edge[u][i];
int v = e.first;
int w = e.second;
if(v == fa) continue;
dfs(v, u, k);
weights.push_back(w + dp[v][0] - dp[v][1]);
sum += dp[v][1];
}
//leaf
if(weights.size() == 0){
dp[u][0] = dp[u][1] = 0;
}else{
sort(weights.begin(), weights.end());
reverse(weights.begin(), weights.end());
long long max_sum = 0;
for(int i = 0;i < k - 1 && i < weights.size(); ++i){
if(weights[i] <= 0) break;
max_sum += weights[i];
}
dp[u][0] = dp[u][1] = max_sum + sum;
if(weights.size() >= k && weights[k - 1] > 0)
dp[u][1] += weights[k - 1];
}
}
int main()
{
int q,n,k,u,v,w;
scanf("%d", &q);
while(q--){
scanf("%d%d", &n, &k);
for(int i = 0;i <= n; ++i) edge[i].clear();
for(int i = 1;i < n; ++i){
scanf("%d%d%d", &u, &v, &w);
edge[u].push_back(make_pair(v,w));
edge[v].push_back(make_pair(u,w));
}
if(k == 0) {puts("0"); return 0;}
dfs(1, -1, k);
printf("%I64d\n", dp[1][1]);
}
return 0;
}
1223F Stack Exterminable Arrays
题意:一个数组称其为Stack Exterminable,如果它按顺序一个个进栈,遇到和粘顶相同的就消去,当所有数都进栈都消光。现给定一个数组,问其中由多少个Stack Exterminable子数组(连续段)。
题解:如果我们能求出以每个位置结束的Stack Exterminable子数组数目,答案就是所有位置数目的和。
我们设dp[i]为以i位置结束的Stack Exterminable子数组数目。
设当前字符为a[i],我们找到前面最近的一个位置j,a[j]=a[i],且j 到i能够消去。则dp[i] = dp[j - 1] + 1。
这是因为以i为结尾的,如果可以消去,那么i肯定是和j消去的。首先i肯定不能够和j后面的消去,不然的话和j的定义矛盾。再者如果i和j前面的消去意味着j和另外一个消掉了,矛盾如下图。(你将进栈和出栈调换一下也可以看出)
所以问题就变成,对于每个i,我们找和它匹配的j。怎么找?
如果i和j匹配,要么j = i - 1。要么j + 1到i - 1能够匹配。
所以我们很容易能够写出(prev为我们要找的位置j)
prev = i - 1;
while(prev >= 0){
if(a[i] == a[prev]){
match[i] = prev;
break;
}
prev = match[prev] - 1;
}
就是从i - 1不停往前跳(相当于从后面开始进栈)。但是这样会有大量的跳转,会耗费大量时间。怎么样防止跳转呢? 我们每个位置用一个map来记录。map[i][a]表示从i这个位置往前进行匹配的Stack Exterminable子数组中,前一个为a的位置(a的位置)。则我们从map[i - 1][a[i]]就能得到位置j了。
问题是map[i]怎么求?
map[i] = map[j - 1]再并上map[i][a[i]] = i
但是直接每个位置都求一个map是不行的,需要大量的存储和赋值。我们可以发现每个被后面的匹配过的位置,它是不会再被匹配的。因为按进栈顺序它不会留到后面去。所以这个位置的前一个位置的map就不会再用到,所以我们可以将j - 1位置的map直接给i用。下面是两种做法,一种采用交换,一种记录map的id,累计用到map id(第二个是其他人的代码)
由于每个点只能至多被后面一个位置匹配,所以每个位置最多只能在map中出现一次。所以map的占用空间是O(n)
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 1;
int a[N], dp[N];
map<int,int> match[N];
int main(){
int q,n;
cin >> q;
while(q--){
scanf("%d", &n);
match[0].clear();
for(int i = 1;i <= n; ++i) scanf("%d", &a[i]), match[i].clear();
int cur_match;
long long ans = 0;
memset(dp, 0, (n + 1) * sizeof(int) );
for(int i = 1;i <= n; ++i){
cur_match = -1;
if(match[i - 1].count(a[i])){
cur_match = match[i - 1][a[i]];
if(cur_match > 0)
swap(match[i], match[cur_match - 1]);
}
match[i][a[i]] = i;
if(cur_match > 0) dp[i] = 1 + dp[cur_match - 1];
ans += dp[i];
}
printf("%I64d\n", ans);
}
return 0;
}
第二种
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
int q,n,a[300030],id[300030],idnum,last[300030],dp[300030];
long long ans;
map<int,int>mp[300030];
int main()
{
scanf("%d",&q);
while(q--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
ans=0;
memset(last+1,-1,n<<2);
for(int i=1;i<=n;++i)
{
if(a[i]==a[i-1])
{
last[i]=i-2;
if(id[i-2])id[i]=id[i-2];
else id[i]=++idnum;
mp[id[i]][a[i-2]]=i-2;
}
else if(mp[id[i-1]].count(a[i]))last[i]=mp[id[i-1]][a[i]]-1,id[i]=id[last[i]]?id[last[i]]:++idnum,mp[id[i]][a[last[i]]]=last[i];
if(~last[i])dp[i]=dp[last[i]]+1,ans+=dp[i];
// printf("%d %d %d\n",last[i],dp[i],id[i]);
}
printf("%lld\n",ans);
memset(dp+1,0,n<<2);
memset(id+1,0,n<<2);
for(int i=1;i<=idnum;++i)mp[i].clear();idnum=0;
}
return 0;
}