A 組隊比賽
題目類型:思維
題目鏈接:
題目大意:
給你四個數組,兩兩一組,每組實力值爲隊內實力值之和,求解兩組差值最小
解題思路:
四個數組排序,最大與最小,第二與倒二,絕對值下差值即可
代碼:
int a[4];
int main(){
cin >> a[0] >> a[1] >> a[2] >> a[3];
sort(a, a+4);
cout << abs(a[0] + a[3] - a[1] - a[2]) << '\n';
}
B 每日一報
題目類型:模擬、排序
題目鏈接:
題目大意:
給你一份n個學生的數據,裏面包含學生的測溫時間、學號、體溫三個信息,現在要求你取出大於38.0的學生信息,並按照如下要求進行排序
鏈接:https://ac.nowcoder.com/acm/contest/5278/B
來源:牛客網
報送日期不一致的,則日期較近的在上,日期較久遠的在下;
報送日期一致體溫不一致的,則體溫高的在上,體溫低的在下;
報送日期和體溫都一致的,則學號小的在上,學號大的在下。
解題思路:
通過結構體排序實現
代碼:
struct sit{
string day, num; DB tem;
} a[101];
bool cmp (sit fir, sit sed){
if (fir.day != sed.day) return fir.day > sed.day;
else if (fir.tem != sed.tem) return fir.tem > sed.tem;
else {
return fir.num < sed.num;
}
}
int main(){
int n, k = 0; cin >> n;
REP(i, n){
string rday, rnum; DB rtem;
cin >> rday >> rnum >> rtem;
if (rtem >= 38.0) a[k].day = rday, a[k].num = rnum, a[k].tem = rtem, k++;
}
OT(k);
sort(a, a+k, cmp);
REP(i, k) {
cout << a[i].day << " " << a[i].num << " "; printf("%.1f\n", a[i].tem);
}
}
C 最長非公共子序列
題目類型:思維
題目鏈接:
題目大意:
給你兩段字符串,現在要求你求解,最長不相同子序列的長度
解題思路:
其實很簡單,原比最長公共子序列容易的多,兩個字符串完全相同的情況下是不可能有不相同子序列的,而一但兩個字符串不同直接輸出較長那個字符串的就可以了,簡單的思維題
代碼:
int main(){
string s1, s2; cin >> s1 >> s2;
if (s1 == s2) cout << "-1" << '\n';
else cout << max(SZ(s1), SZ(s2)) << '\n';
}
D 組隊比賽
題目類型:思維
題目鏈接:
題目大意:
給你一個n,要求你求解出最多01字符串,這些字符串要求相互不是子串,且任意兩兩長度不同
解題思路:
實際上當你寫幾個01字符串你會發現幾個要點
- 1或0會是所有長度大於等於的字符串的子串(這一點是告訴你要特判n=2的情況)
- 兩個1中間不斷加0比不可能是上一串的子串
針對第二個我寫幾個,就很好理解了,
11
101
1001
10001
100001
你會發現恰好滿足長度不一致,且不互爲子串的條件(00也一樣),所以只需要特判n=1和n=2的情況就可以了
代碼:
int main(){
int n; cin >> n;
if (n == 1){ cout << 1 << '\n' << 1 << '\n';}
else if (n == 2) cout << "2\n" << 0 << '\n' << "11" << '\n';
else { cout << n - 1 << '\n';
REP(i, n - 1){
cout << "1"; REP(j, i){cout << "0";} cout << 1 << '\n';
}
}
}
E 組隊比賽
題目類型:思維
題目大意:
給你一個長度爲n的數組,每個位置的值表示美味度(即),你可以從頭或者從尾食用,一秒只能食用一個,並且未被食用的部分會美味度-1,要求你求解最大的總美味度
解題思路:
這題你可能會思考,到底是從頭吃呢還是從尾吃呢,其實是沒差別的,因爲
他用數字標識了每個部分的美味度(可能是負的)
美味度可能是負值,你要減去的數量就一定會
可以邊加邊減,也可以全部加完再減
代碼:
有人可能會是第一種方法,卻wa了,n必須要是是LL,爲什麼n要是LL呢?
明明題目給的數據沒有溢出啊,是因爲在計算的時候溢出的,這個可能導致你的wa(還好我用的是第二個方法QAQ)
int main(){
LL n; cin >> n;
LL ans = 0;
REP(i, n){
LL x; scanf("%lld",&x);
ans += x;
}
ans -= (n - 1) * n / 2;
OT(ans);
}
int main(){
int n; cin >> n;
LL ans = 0;
REP(i, n){
int x; scanf("%d",&x);
ans += x - i;
}
OT(ans);
}
F 組隊比賽
題目鏈接:
題目類型:模擬
題目大意:
母親節和父親節你需要送禮,對於每一個日期,輸出下一個需要送禮的日子(下一個需要送禮的日子可能是母親節也可能是父親節)
解題思路:
依照模擬,需要判斷的實際上就是下一個節日是什麼,
母親節在8到14之間變動
if(a[i] < 8) a[i] += 7;
,父親節在15到21之間變動,
if(b[i] < 15) b[i] += 7;
代碼:
const int maxn = 200;
int a[maxn], b[maxn];
int main(){
int n = 0, p;
a[0] = 14;
b[0] = 18;
FOR_1(i, 1, 103)
{
a[i] = a[i-1]-1;
b[i] = b[i-1]-1;
if(i % 4 == 0 && i != 100)
{
a[i]--;
b[i]--;
}//閏年
if(a[i] < 8) a[i] += 7;
if(b[i] < 15) b[i] += 7;
}
int _; for(scanf("%d", &_); _; _--){
int y, m, d;scanf("%d%d%d",&y,&m,&d); n = y - 2000; //易知是從2000年開始
if(m > 6 || m == 6 && d >= b[n])
printf("Mother's Day: May %dth, %d\n",a[n+1], y+1);
else if(m < 5 || m == 5 && d < a[n])
printf("Mother's Day: May %dth, %d\n",a[n], y);
else if(b[n]!=21)
printf("Father's Day: June %dth, %d\n",b[n], y);
else printf("Father's Day: June 21st, %d\n", y);
}
}
G 血壓遊戲
題目鏈接
題目類型:樹
題目大意:
官方題意
Compute 有一棵 n 個點,編號分別爲 1∼n 的樹,其中 s 號點爲根。
Compute 在樹上養了很多松鼠,在第 i 個點上住了 ai 個松鼠。
因爲某些緣故,它們開始同時向根節點移動,但它們相當不安分,如果在同一個節點上,它們就會打起來,簡單地來說以下事件會依序發生:
如果一個節點上有 2 只或 2 只以上的松鼠,他們會打架,然後這個節點上松鼠的數量會減少 1;
根節點的所有松鼠移動到地面,位於地面上的松鼠不會再打架;
所有松鼠同時朝它們的父節點移動。
所有事件各自都在一瞬間完成,直至樹上沒有松鼠。
現在 Compute 想知道最終有多少隻松鼠到達了地面。
亂搞題意
給你一個樹,每個節點上都有個松鼠,所有位置上的松鼠都是向父節點進行移動的,而根節點上的松鼠是向地面上移動,就是從根節點移動出去
每次的操作時這樣的
- 如果一個節點上有 2 只或 2 只以上的松鼠,他們會打架,然後這個節點上松鼠的數量會減少 1;
- 根節點的所有松鼠移動到地面,位於地面上的松鼠不會再打架;
- 所有松鼠同時朝它們的父節點移動。
題目問的是最後到地面上的松鼠有多少隻
解題思路:
結論1
由於每一步的松鼠都是向父節點移動的,只有一個節點上有2只或者兩隻以上的松鼠纔會打架,那麼可以確定 只有同一層上的松鼠纔會相互打架
- 因爲是同一時間進行向父節點進行移動的
思考1
由於是子節點向父節點進行移動所以可以考慮子樹合併的思想,(可以思考是不是樹上啓發式合併的方法)
思考2
由於是不斷的向上移動,也就是每個節點需要不斷的查詢,那麼可能會面臨較多次數的查詢次數, 可以思考是不是需要使用虛樹這個數據結構呢?
虛樹中包含了所有的關鍵點,也包含了所有關鍵點兩兩之間的 LCA(lowest common ancestor, 最近公共祖先)(LCA 的數量不超過關鍵點的個數,稍後有證明),這就保證了虛樹不會喪失原有的樹形結構,同時儘可能地壓縮了樹的大小。同時虛樹中 uu 到 vv 的邊權定義爲原樹中 uu 到 vv 的最短路徑
關於虛樹虛樹學習筆記
dp[x]=∑t∈sonx ,dp[t]>0max{1,dp[t]−(deep[t]−deep[x])}
for(auto k : ma[rt])
if(k.second)
ans += max(1ll, k.second-tag[rt]);
總結
方法1 .利用DFS序合併區間虛樹的線段樹 && 啓發式合併的思想
方法2. 虛樹構建一下,再進行dp,根據子節點情況去計算父節點情況(將子節點合併到父節點上),滿足通過計算有限集統計總和情況
樹上合併式啓發
void merge(int x, int y) {
int xx = find(x), yy = find(y);
if (size[xx] < size[yy]) swap(xx, yy);
fa[yy] = xx;
size[xx] += size[yy];
}
虛樹
我們將兩個大小不一樣的集合,我們將小的集合合併到大的集合中,而不是將大的集合合併到小的集合中。
爲什麼呢?這個集合的大小可以認爲是集合的高度(在正常情況下),而我們將集合高度小的併到高度大的顯然有助於我們找到父親
讓高度小的樹成爲高度較大的樹的子樹,這個優化可以稱爲啓發式合併算法。
代碼:
const int maxn = 200100;
int n, rt;
LL ans;
int a[maxn];
vector<int> G[maxn];
int fa[maxn], dep[maxn];
LL tag[maxn];
map<int, long long> ma[maxn];
void inse(int u, int d, long long val)
{
if(!ma[u].count(d))
ma[u][d] = val+tag[u];
else
ma[u][d] = max(ma[u][d], tag[u]+1) + val;
}
void merg(int u, int v)
{
if(ma[v].size() > ma[u].size())
{
swap(ma[u],ma[v]);
swap(tag[u],tag[v]);
}
for(auto i : ma[v])
if(i.second)
inse(u, i.first, max(i.second-tag[v], 1ll));
}//將小的子樹合併到大的父節點中
void dfs(int u)
{
dep[u] = dep[fa[u]] + 1;
for(int v : G[u])
{
if(v == fa[u]) continue;
fa[v] = u;
dfs(v);
merg(u, v);
}
if(a[u])
inse(u, dep[u], a[u]);
tag[u]++;
}
int main()
{
scanf("%d%d", &n, &rt);//點的數量和根的編號
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 2; i <= n; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(rt);
for(auto k : ma[rt])
if(k.second)
ans += max(1ll, k.second-tag[rt]);
printf("%lld\n", ans);
}
H 紙牌遊戲
題目鏈接:
題目類型:數學
題目大意:
給你一個字符串s 要求你拼出一個長度爲 k 的同時能被 3 整除的最大非負整數,並且不能有前導0
解題思路:
什麼樣的數能被3整除?
該數字的各個位置上的數相加之和能被3整除,那麼該數也一定能被3整除
注意
一開始我求簡便,是這麼做,因爲要最大,所以我直接排序,取較大的k個數字,然後判斷是否能被3整除,如果不行我選一位去枚舉和後面一位交換直到成立,這麼做是不對的,因爲當需要出現交換兩個的時候就會出錯,這裏給出一下樣例,有興趣的可以跑一下
輸入
998244151 3
輸出
984
正文
1.我們因爲每位字符純粹是我們自己決定的,沒有什麼其他的要求,所以我們完全可以統計0-9數字出現的次數去進行組合
2.取餘3爲1的數字和取餘3位2的數字組合之和一定能被3整除,例如2和1
3.我們在組合區間枚舉出符合條件最大的就可以
代碼:
int cntmod[3]; // 被3取餘後只會有3種情況 0, 1, 2,這裏對三種情況進行一下計數
int cnt[10];
bool judge(int x, int m){
if (!m) return !x;
int a = cntmod[0], b = cntmod[1],c = cntmod[2];
int l0 = max(0, m - b - c), r0 = min(a, m);
//一個除3餘數爲1的數和一個除3餘2的數匹配就是一個能被3整除的數,例如1和2, 那麼我們就枚舉這些組合判斷是否能被整除
for(int i = l0; i <= r0; i++){
int t = ((2 * (m - i) - x) % 3 + 3 ) % 3;
int l1 = max(0, m - i - c), r1 = min(b, m - i);
while(l1 % 3 != t) l1++;
if(l1 <= r1) return true;
}
return false;
}
void init(){
//初始化
memset(cntmod, 0, sizeof cntmod);
memset(cnt, 0, sizeof cnt);
}
int main(){
int _;
for(scanf("%d", &_); _; _--){
init();
string s; int k;
cin >> s >> k;
int len = SZ(s);
FOR(i, 0, SZ(s)){
cnt[s[i] - '0']++; cntmod[(s[i] - '0') % 3]++;
}
//統計
int n;
for(int i = 9, j = n = 0; i >= 0; i--){
//枚舉各個數出現次數之前已經統計過了,現在只是運用
while(cnt[i] > 0 && n < k){
int t = (i + j) % 3;
cnt[i]--, cntmod[i % 3] --;//如果這數字字i使用了,就減去
if (!judge((3 - t) % 3, k - n - 1)){
cnt[i]++, cntmod[i % 3]++;
break;//如果不能被整除那麼就回溯,不使用這個i
}
s[++n] = '0' + i;j = t;
}
}
//s[n + 1] = '\0';
if(n < k || (s[1] == '0' && n > 1)) OT("-1");//出現前導0的情況
else {
FOR_1(i, 1, n){
cout << s[i];
}
OT("");
}
}
}