1223A CME
題意:給定一個數,把它分成三個數a,b,c使得a + b = c。問至少要增加多少才能分?
題解:
當n==2時,至少需要2。因爲最小的等式是1+1=2。
當n>2分兩種情況 ,n爲偶數時,令即可
當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個正整數,以及, 還有
對於這n個數的一個排列,對每個的倍數的位置,我們能得到值,對於每個倍數的位置,我們能得到值。問在這個n個數裏面至少取多少個數能使得到的值的和至少爲
題解:二分+貪心。二分取的數字個數。先把數從大到小排序,把大的數優先排在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個數的二進制
怎麼求這個最大值呢? 注意到上式等於
我們可以取數組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中第個(如果大於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;
}