比賽總結
這場打的太難頂了,通過這場可以很明顯看出我隊綜合水平不行,和其他隊伍的差距太大了,前55名過題數都是在我們的3倍及以上
由於我們綜合水平低,且比賽時狀態也不好,寫題的策略也錯了,導致這場2題打鐵
從L題講起
L
看到榜上這么多人過L后,我們也就跟榜開這題了,當時我們是3個人一起想這題,還是在不算比較快,也不算比較慢的速度想到了LDS,但是代碼實現卻花了挺長時間,基礎的LIS和LDS很久沒寫了,非常生疏了,A這道題時已經在64min,也算
是我板子准備的不夠充分,題練的太少了,LIS我大概是一年沒見過了,而且我到現在也沒寫過用樹狀數組求LIS,比賽時只在那想開一個棧然后怎么樣怎么樣
L題之后就是我寫的 I 題了
I
計算幾何是我們隊的短板,當時也是跟榜寫的這題,聽隊友講了題意后感覺做法挺簡單的,雖然都不會計算幾何,但是看這做法這么簡單,我就上了,寫的也挺折磨的,我用的是斜截式,寫完后wa了幾發,就去看其它題了,這時候已經130min了,落后了很多,不應該寫這題的,計算幾何是我們隊的不適題,而且比賽才過了第1個小時,應該先去開其他題的,寫這題不僅耗時,還要看運氣,應該放到后面去寫
我放棄 I 題一段時間后,隊友很勇敢的上去寫麻將題
K
麻將大模擬題,隊友寫了很久,都快300行了,非常辛苦,但是一直wa調不出來,賽后才發現題都讀錯了。。我不會打麻將,也沒寫過麻將題,這題就讓隊友承包了。我們幾乎沒寫過大模擬,我如果寫麻將這種題肯定是感覺很可怕就直接跳過,比賽時也不應該那時去寫這題,因為這種題也是我們隊的不適題,所耗時間以及風險都很大,而且還有好多題沒有去思考,這題真的應該放到最后去寫
I題和K題是我們這次失敗的主要原因,隊友寫K題的時候,我才開始思考其他題目,因為我之前是在寫L題和 I 題,而 I 題讓我思考的時間少了一個小時,K題使隊友的思維時間少了兩個小時左右,而我和他是隊里的主要思維輸出,跟榜去寫這兩
題損失就很大了
J
隊友寫麻將前和我講了這題,可惜我沒有領會隊友的意思,到最后還不知道pairwise distinct的意思,我到賽后才知道是原來是求最少要多少輪把它排好序,比賽時應該好好再去看題目意思的,以后要養成一個習慣,就是隊友轉述題目意思后,自
己還得仔細讀一下題面,由於我沒有讀懂這道題的題意,就放掉這題了,否則隊友寫麻將的時間內我絕對可以想出這題怎么寫
沒有去開J題的我,向另一隊友要了M題
M
中規中矩的數據結構題,觀察出mex的性質后,要研究的問題就是怎么求一段區間小於等於mex的數之和,我在隊友寫麻將的時間里花了很長時間想出來怎么求,想出來了一個線段樹做法,我自己也擅長寫線段樹板子的(指計時速寫過幾次線段
樹模板二),我自己的板子是用動態開點的指針寫法的,當時看到1個G內存真沒想到會MLE,在這之后也沒想到把它改寫成非指針的寫法,因為當時感覺NODE里開了這么多long long數組,以為已近沒戲了
想出來M題后,我開始想C題
C
中規中矩的區間dp題,隊友轉述給我后,我並沒有再去把題目條件仔細看一遍,只看了下N的范圍,構造了一些情況后覺的是個N^2的dp,然后自己想了下轉移方程,實際上我比賽時想的轉移方程想錯了,而且我比賽時並沒有看到相同城市數<=15這個條件。。因為寫M題時卡住了,沒時間寫這題了
下面的是比賽時沒有去看的兩道題
G
背包加一些其他東西的題,碼量比較大的背包。
D
有意思的數學題,可以想出一個必要條件。
綜合水平低,體現在L題對LDS的不熟練,I題計算幾何的短板,M題不知道是主席樹,C題對區間dp的練習少。賽后補題也發現G題這個背包我也是不會這個帶有時間限制的狀態轉移。
如果比賽時不去開 I 題和K題,那么J題是很可能被做出來的,M題和C題不一定能做出來,M題需要我想到把線段樹改寫成不用指針的形式,C題則需要一起合作造樣例查錯才能做出來,賽后補題的時候我一直沒注意C題要清空vector。
除了H題,我們其他所有題都是有水平上的不足,所以接下來得努力練習,努力學習
補題
M題
先講一下我比賽時想出來的做法,首先觀察出mex的性質,就是mex可以被 區間里小於等於mex的數之和+1 更新,反復更新mex,直到mex不能被更新為止
所以要想辦法快速求出一段區間里所有小於等於x的數之和,我開了一個線段樹,用來計算一段區間內小於等於(1<<i)的數之和(記為su[i]),和一段區間內大於(1<<i)的最小的數(記為mi[i])
記區間里小於等於x的數之和為low(x),區間里大於x的最小的數為min(x),比如初始時mex = 1,假設low(1) + 1 == 3,則更新mex = 3,假設low(2) + 1 == 6,更新mex = 6,假設low(4) + 1 == 6,此時low(4) + 1不能更新mex,那怎么判斷low(6) + 1能否更新mex? 如果min(4) > 6,那么low(6) = low(4),則不能更新,如果min(4) <= 6,則low(6) >= low(4) + min(4) > low(4) + 4 > 8,那么就能更新mex,且mex是>8的,所以就能用low(8) + 1 來更新mex了
M(非主席樹)
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e6 + 7;
const long long INF = 1e9 + 7;
struct NODE {
int l, r;
long long su[31], mi[31];
}tree[MAXN*4];
void upd(int pos) {
for (int i = 0; i < 31; i++) {
tree[pos].su[i] = tree[pos << 1].su[i] + tree[pos << 1 | 1].su[i];
tree[pos].mi[i] = min(tree[pos << 1].mi[i], tree[pos << 1 | 1].mi[i]);
}
}
void build(int pos, int l, int r) {
tree[pos].l = l; tree[pos].r = r;
if (l == r) {
long long v;
scanf("%lld",&v);
for (int i = 0; i < 31; i++) {
if (v > ((long long)1 << i)) {
tree[pos].su[i] = 0;
}
else tree[pos].su[i] = v;
}
for (int i = 0; i < 31; i++) {
if (v <= ((long long)1 << i)) tree[pos].mi[i] = INF;
else tree[pos].mi[i] = v;
}
return;
}
int mid = l + r >> 1;
build(pos<<1, l, mid);
build(pos<<1|1, mid + 1, r);
upd(pos);
}
long long Q(int pos, int l, int r, int i) {
if (tree[pos].l == l && tree[pos].r == r) return tree[pos].su[i];
int mid = tree[pos].l + tree[pos].r >> 1;
if (r <= mid) return Q(pos<<1, l, r, i);
else if (l > mid) return Q(pos<<1|1, l, r, i);
else return Q(pos<<1, l, mid, i) + Q(pos<<1|1, mid + 1, r, i);
}
long long Q_mi(int pos, int l, int r, int i) {
if (tree[pos].l == l && tree[pos].r == r) return tree[pos].mi[i];
int mid = tree[pos].l + tree[pos].r >> 1;
if (r <= mid) return Q_mi(pos << 1, l, r, i);
else if (l > mid) return Q_mi(pos << 1 | 1, l, r, i);
else return min(Q_mi(pos << 1, l, mid, i), Q_mi(pos << 1|1, mid + 1, r, i));
}
int main()
{
int n, m;
cin >> n >> m;
build(1, 1, n);
int l, r;
long long ans, mex;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &l, &r);
l = ((long long)l + ans) % n + 1;
r = ((long long)r + ans) % n + 1;
if (l > r) swap(l, r);
mex = 1;
for (int i = 0; i < 31; i++) {
if (mex >= ((long long)1 << i)) mex = max(mex, Q(1, l, r, i) + 1);
else if(i){
long long lim = Q_mi(1, l, r, i - 1);
if (lim != INF && lim > mex) break;
else {
mex = max(mex, Q(1, l, r, i) + 1);
}
}
}
ans = mex;
printf("%lld\n", ans);
}
return 0;
}
用主席樹可以求出一段區間內值域在[l,r]內的數之和
M(主席樹)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
const int MAXN = 1e6 + 7;
const int MAX = 1e9;
int n, m, cnt = 0, root[MAXN], x, y;
long long a[MAXN];
struct NODE {
int l, r, siz;
long long su;
}T[MAXN * 43];
void update(int l, int r, int& x, int y, long long pos) {
T[x = ++cnt] = T[y], T[x].siz++, T[x].su += pos;
if (l == r) return;
int mid = l + r >> 1;
if (pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
else update(mid + 1, r, T[x].r, T[y].r, pos);
}
long long Q(int l, int r, int x, int y, int pos) {
if (pos >= r) return T[y].su - T[x].su;
if (pos < l) return 0;
if (!x && !y) return 0;
int mid = l + r >> 1;
return Q(l, mid, T[x].l, T[y].l, pos) + Q(mid + 1, r, T[x].r, T[y].r, pos);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for (int i = 1; i <= n; i++) update(1, MAX, root[i], root[i - 1], (long long)a[i]);
long long mex = 0, res = 0;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &x, &y);
x = ((long long)x + mex) % n + 1;
y = ((long long)y + mex) % n + 1;
if (x > y) swap(x, y);
mex = 1;
while (1) {
res = Q(1, MAX, root[x - 1], root[y], min((long long )MAX,mex)) + (long long)1;
if (res <= mex) break;
mex = res;
}
printf("%lld\n", mex);
}
return 0;
}
主席樹可以不離散化
C題
很容易想到縮點,且想到ans<= 縮點后的點的個數-1,但是這之后就難了,發現可以利用相同的顏色使答案減少1
比如1xxxxx1xxx,可以使ans - 1,1xxxx1xxx2xxxx2可以使ans - 2,也容易發現 1xx2xx1xx2這種只能使ans - 1,因為兩個相同的1和兩個相同的2只能選其中一個利用
那么我們要求的是 能夠同時利用來減少答案的兩個同色點對的對數最多是多少對
而且通過構造樣例發現,對於1xxx1xxx1,能利用的對數是2對!!1xx1xx1xx1是3對!!
用dp[L][R]表示L,R區間里最多能選多少對,可以利用題目給的每一個顏色最多出現15次的條件,把O(n^3)優化成O(15 n^2)
縮點及存位置:
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[i].clear();
}
int tot = 0;
a[++tot] = a[1];
for (int i = 2; i <= n; i++) {
if (a[i] != a[tot]) a[++tot] = a[i];
}
for (int i = 1; i <= tot; i++) {
pos[a[i]].push_back(i);
}
dp[][]的轉移:
for (int i = 1; i <= tot; i++) {
for (int j = 1; j <= tot; j++) {
dp[i][j] = 0;
}
}
for (int len = 2; len <= tot; len++) {
for (int l = 1; l <= tot; l++) {
int r = l + len - 1;
if (r > tot)break;
if (a[l] == a[r]) dp[l][r] = dp[l + 1][r - 1] + 1;//比[l+1][r-1]多利用了a[l]和a[r]這一對
else dp[l][r] = max(dp[l][r - 1], dp[l + 1][r]);
for (int i = 0; i < pos[a[l]].size(); i++) {
int pp = pos[a[l]][i];
if (pp <= l) continue;
if (pp >= r) break;
dp[l][r] = max(dp[l][r], dp[l][pp] + dp[pp][r]);
}
}
}
cout << tot - 1 - dp[1][tot] << endl;
同時也可以用正着算的方法,用f[L][R]表示[L][R]區間的最少操作數是多少
那么枚舉中間點的時候怎么轉移呢
f[L][R] = min(f[L][R], f[L][mid] + f[mid+1][R] )?
還是f[L][R] = min(f[L][R], f[L][mid] + f[mid+1][R] + 1)?
這個地方 +1還是 不+1 ,怎么處理?
可以發現,f[L][R]同時也可以表示:把[L][R]全部染成a[L]的顏色的最少操作數
所以,當mid+1的顏色和L相同時,就可以不用+1,而我們取mid時,只取mid+1和L同顏色的情況,這樣就ok了!
for (int i = 1; i <= tot; i++) {
for (int j = 1; j <= tot; j++) {
f[i][j] = INF;
}
f[i][i] = 0;
}
for (int len = 2; len <= tot; len++) {
for (int l = 1; l <= tot; l++) {
int r = l + len - 1;
if (a[r] == a[l]) f[l][r] = f[l][r - 1];//f[l][r-1]表示把[L][R-1]都染成L的顏色
else f[l][r] = min(f[l][r - 1], f[l + 1][r]) + 1;//如果a[r] != a[l],那就是把[L][R-1]染好再+1或把[L+1][R]染好再+1
for (int i = 0; i < pos[a[l]].size(); i++) {//初始賦值后進入這個循環
int pp = pos[a[l]][i];
if (pp <= l) continue;
if (pp >= r) break;
f[l][r] = min(f[l][r], f[l][pp - 1] + f[pp][r]);//把[L][pp-1]染成L的顏色,把[pp][R]染成pp的顏色,而pp和L同色
}
}
}
cout << f[1][tot] << endl;
J題
如果原排列有序,則round為0
對於一個排列,若只有兩個數位置不對,則把這兩個數交換即可
對於一個排列,若只有三個數位置不對,如2 3 1, 2想要到3的位置去,3想要到1的位置去,1想要到2的位置去,於是這便構成了一個環
於是掃一遍原排列,則可構成一些互相獨立的環,這個環可以用雙向鏈表表示出來
現在考慮把一個環內兩個數交換一次會發生什么,可以發現會把這個環拆成兩個環,兩個環的size之和等於原環的size,且這兩個環中各有一個數不能在這一個round里交換位置了
於是我們可以把一個大環在一個round里把它拆成若干個2元環和一個一元環,然后在下一個round里把所有二元環解決掉
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 7;
int a[MAXN],pre[MAXN];
bool vis[MAXN];
int cnt[103];
int ans[103][MAXN];
int rd = 0;
int n;
void add(int x,int y) {
vis[x] = vis[y] = true;
ans[rd][++cnt[rd]] = x;
ans[rd][++cnt[rd]] = y;
swap(a[x], a[y]);
swap(pre[x],pre[y]);
}
void solve(int x) {
int len = 0;
while (!vis[x]) {
vis[x] = true;
len++;
x = a[x];
}
int t = x;
for (int i = 1; i <= len; i++) {
vis[t] = false;
t = a[t];
}
int y = a[x];
while (1) {
if (vis[x] || vis[y]) break;
if (x == y) break;
int xx = pre[x], yy = a[y];
add(x, y);
x = xx; y = yy;
}
}
bool ok() {
for (int i = 1; i <= n; i++) if (a[i] != i) return false;
return true;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pre[a[i]] = i;
}
rd = 0;
while (!ok()) {
rd++;
cnt[rd] = 0;
for (int i = 1; i <= n; i++) {
vis[i] = false;
}
for (int i = 1; i <= n; i++) vis[i] = false;
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
solve(i);
}
}
}
cout << rd << "\n";
for (int i = 1; i <= rd; i++) {
cout << cnt[i] / 2;
for (int j = 1; j <= cnt[i]; j++) {
cout << " " << ans[i][j];
}
cout << "\n";
}
return 0;
}
I題
求交點然后sort就行,注意判段交點在直線上的eps的問題,缺少計算幾何的板子就很難通過這題了
隊友的代碼:(我們終於也會用計算幾何的板子啦)
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define double long double
// #define endl "\n"
const int MAXN = 1E3 + 7;
// const int MAXE = ;
// const int MOD = ;
// const int INF = ;
const double eps = 1e-8;
// const double PI = acos(-1);
// const int DIRX[] = {};
// const int DIRY[] = {};
int sgn(double x)
{
return fabs(x) < eps ? 0 : (x < 0 ? -1 : 1);
}
struct Point
{
double x, y;
Point() {};
Point(double x, double y) : x(x), y(y) {}
Point operator + (const Point& b) { return Point(x + b.x, y + b.y); }
Point operator - (const Point& b) { return Point(x - b.x, y - b.y); }
Point operator * (double d) { return Point(x * d, y * d); }
double dot(const Point& b) { return x * b.x + y * b.y; }
double det(const Point& b) { return x * b.y - y * b.x; }
double dis(const Point& b) { return (*this - b).dot(*this - b); }
bool operator < (const Point& b) const
{
if (x == b.x)
return y < b.y;
return x < b.x;
}
} s, t, point[MAXN];
typedef Point Vector;
double crossProduct(Vector a, Vector b)
{
return a.x * b.y - a.y * b.x;
}
double dotProduct(Vector a, Vector b)
{
return a.x * b.x + a.y * b.y;
}
bool pointOnSegment(Point p, Point a, Point b)
{
// sgn(crossProduct(a - p, b - p)) == 0 may cause eps problem.
// If p is on the line, for check on the segment, ignore this condition.
// return sgn(crossProduct(a - p, b - p)) == 0 && sgn(dotProduct(a - p, b - p)) < 0;
return sgn(dotProduct(a - p, b - p)) < 0;
}
Point lineIntersection(Point p, Vector u, Point q, Vector v)
{
// Line 1: I = p + t * u
// Line 2: I = q + s * v
Vector w = p - q;
double t = crossProduct(v, w) / crossProduct(u, v);
return p + u * t;
}
int n, m;
int cnt[MAXN];
vector<Point> ans[MAXN];
signed main(void)
{
cin >> n >> m;
cin >> s.x >> s.y >> t.x >> t.y;
for (int i = 1; i <= n; ++i)
cin >> point[i].x >> point[i].y;
for (int i = 1; i <= n; ++i)
{
for (int j = i + 1; j <= n; ++j)
{
if (sgn((point[i] - point[j]).det(s - t)) == 0) // check Parallel
continue;
Point p = lineIntersection(point[i], point[j] - point[i], s, t - s);
if (pointOnSegment(p, s, t) == true)
{
ans[i].push_back(p);
ans[j].push_back(p);
++cnt[i]; ++cnt[j];
}
}
}
for (int i = 1; i <= n; ++i)
{
sort(ans[i].begin(), ans[i].end());
if (sgn(t.x - s.x) < 0 || (sgn(t.x - s.x) == 0 && sgn(t.y - s.y) < 0)) // sort condition: nearest point T
reverse(ans[i].begin(), ans[i].end());
}
int x, y;
for (int i = 1; i <= m; ++i)
{
cin >> x >> y;
if (cnt[x] >= y)
printf("%.10f %.10f\n", ans[x][y - 1].x, ans[x][y - 1].y);
else
cout << -1 << endl;
}
return 0;
}
K題
隊友比賽時讀錯題了,把題讀難了QAQ
我還不懂麻將,什么時候去入坑雀魂qwq
隊友的代碼:
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define double long double
// #define endl "\n"
// const int MAXN = ;
// const int MAXE = ;
// const int MOD = ;
// const int INF = ;
// const double eps = ;
// const double PI = acos(-1);
// const int DIRX[] = {};
// const int DIRY[] = {};
string s;
int cnt[5][13];
vector<string> ans;
void pre(string& s)
{
memset(cnt, 0, sizeof(cnt));
int x, y;
for (int i = 0; i < 14; ++i)
{
y = s[i * 2] - '0';
char ch = s[i * 2 + 1];
if (ch == 'w')
x = 1;
if (ch == 'b')
x = 2;
if (ch == 's')
x = 3;
if (ch == 'z')
x = 4;
++cnt[x][y];
}
}
string mahjong(int x, int y)
{
string res = "";
res += (char)(y + '0');
if (x == 1)
res += 'w';
if (x == 2)
res += 'b';
if (x == 3)
res += 's';
if (x == 4)
res += 'z';
return res;
}
bool ron(int x, int y, bool flg, int sum)
{
if (flg == true && sum == 4)
return true;
if (x == 4 && y == 8)
return false;
if (x != 4 && y == 10)
return ron(x + 1, 1, flg, sum);
if (cnt[x][y] == 0)
return ron(x, y + 1, flg, sum);
if (x != 4 && cnt[x][y] > 0 && cnt[x][y + 1] > 0 && cnt[x][y + 2] > 0)
{
cnt[x][y]--;
cnt[x][y + 1]--;
cnt[x][y + 2]--;
bool ok = ron(x, y, flg, sum + 1);
cnt[x][y]++;
cnt[x][y + 1]++;
cnt[x][y + 2]++;
if (ok)
return true;
}
if (cnt[x][y] > 1 && !flg)
{
cnt[x][y] -= 2;
bool ok = ron(x, y, true, sum);
cnt[x][y] += 2;
if (ok)
return true;
}
if (cnt[x][y] > 2)
{
cnt[x][y] -= 3;
bool ok = ron(x, y, flg, sum + 1);
cnt[x][y] += 3;
if (ok)
return true;
}
return false;
}
void check(string& s)
{
bool flg = false;
for (int i = 1; i <= 4; ++i)
{
for (int j = 1; j <= (i == 4 ? 7 : 9); ++j)
{
if (i == 4 && cnt[i][j] == 0)
continue;
if (i != 4 && cnt[i][j] == 0 && cnt[i][j - 1] == 0 && cnt[i][j + 1] == 0)
continue;
cnt[i][j]++;
if (ron(1, 1, false, 0))
{
flg = true;
s += mahjong(i, j);
}
cnt[i][j]--;
}
}
if (flg == true)
ans.push_back(s);
}
void solve()
{
cin >> s;
pre(s);
if (ron(1, 1, false, 0))
{
cout << "Tsumo!" << endl;
return;
}
ans.clear();
for (int i = 1; i <= 4; ++i)
{
for (int j = 1; j <= (i == 4 ? 7 : 9); ++j)
{
if (cnt[i][j] == 0)
continue;
cnt[i][j]--;
string s = mahjong(i, j) + " ";
check(s);
cnt[i][j]++;
}
}
cout << ans.size() << endl;
for (int i = 0; i < ans.size(); ++i)
{
cout << ans[i] << endl;
}
}
signed main(void)
{
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
G題
G題還是比較難的,輸入要判斷2月29號的問題,而且不能提前continue否則會段錯誤! 同時這題還比較卡時間,看榜可以發現有個8題的隊伍這題慘烈TLE很多發(小聲),而且這題代碼量也是比較大的
m個禮物用二進制枚舉選和不選,記錄選 i 個禮物的可行最大價值,要把每個人按生日排序,dp的時候正序遍歷,像背包那樣轉移
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
int dp[505][367][16];
int mm[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
struct PP {
int c, v, lim;
}pp[505];
struct GT {
int c, v;
}gt[16];
int g[16];
bool cmp(PP a, PP b) {
return b.lim > a.lim;
}
int main()
{
int T, n, m, w;
cin >> T;
while (T--) {
cin >> n >> m >> w;
int tot = 0;
int ye, mon, da;
int ct = 0;
int c, v;
memset(g, 0, sizeof(g));
for (int i = 1; i <= n; i++) {
scanf("%d-%d-%d", &ye, &mon, &da);
cin >> c >> v;
if (mon == 2 && da == 29) continue;
ct++;
int d = 0;
for (int j = 1; j < mon; j++) d += mm[j];
d += da;
if (ye > 2021) continue;
pp[ct].c = c; pp[ct].v = v; pp[ct].lim = d;
}
n = ct;
sort(pp + 1, pp + n + 1, cmp);
for (int i = 1; i <= m; i++) g[i] = 0;
for (int i = 1; i <= m; i++) cin >> gt[i].c >> gt[i].v;
for (int i = 1; i < (1 << m); i++) {
int cnt = 0, co = 0, val = 0;
for (int j = 0; j < m; j++) {
if ((i >> j) & 1) {
cnt++;
co += gt[j + 1].c;
val += gt[j + 1].v;
}
}
if (co <= w)g[cnt] = max(g[cnt], val);
}
for (int i = 0; i <= n; i++) {
for (int day = 0; day <= 365; day++) {
for (int j = 0; j <= m; j++) {
dp[i][day][j] = 0;
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int day = 1; day <= 365; day++) {
for (int j = 0; j <= min(i,m); j++) {
dp[i][day][j] = dp[i - 1][day][j];
if (day >= pp[i].c && day <= pp[i].lim) dp[i][day][j] = max(dp[i][day][j], dp[i - 1][day - pp[i].c][j] + pp[i].v);
if (j) dp[i][day][j] = max(dp[i][day][j], dp[i - 1][day][j - 1] + (g[j] - g[j - 1]));
}
}
}
for (int i = 0; i <= m; i++) {
for (int day = 1; day <= 365; day++) {
ans = max(ans, dp[n][day][i]);
}
}
cout << ans << endl;
}
return 0;
}
D題
講一下題意,就是兩個人組成一對和機器人玩游戲,一局游戲里,給定n,k, 機器人會造一個長度為n的數列,數列中的每個數在[0,k)范圍里,然后把數列和一個數字p(0<=p<n) 交給隊員1,他必須要在數列里選一個數,把這個數+1,
再%k,然后把這個經過修改后的數列交給隊友,但是他不能把數字p告訴隊友,然后隊友會猜數字p是什么,且隊友只能提交一次答案,問是否有必勝策略
如果n為1,那么p只能為0,人類必贏
如果n為2,k為2,那么機器人構造的數列可以是{0,0}或{1,0}或{0,1}或{1,1},機器人給的p可以是0或1
可以構造出一個必勝策略:
如果p是0,機器人構造{0,0}或{1,1},隊員1就把它改成{0,1},機器人構造{0,1}或{1,0},隊員1就把它改成{1,1}
如果p是1,機器人構造{0,0}或{1,1},隊員1就把它改成{1,0},機器人構造{0,1}或{1,0},隊員1就把它改成{0,0}
這樣,隊員2收到{0,1}或{1,1},就輸出0,收到{1,0}或{0,0},就輸出1
機器人構造的數列一共有k^n種情況,把這些數列想象成k^n個點,每個點連接n個點,表示這個點可以通過一次修改可以變成的點,每個點還有一個顏色,一共有n種顏色,分別代表p的位置
於是每個點連接的n個點的顏色一定要是不同的,這樣人類就有必勝策略,而要滿足這個條件,每個點不能連接兩個同樣顏色的點,每個綠色點可以給n個點貢獻綠色,那么綠色點的個數為k^n / n,其他顏色點的個數與其一樣
於是得到一個必要條件,就是(k^n) % n == 0
至於這個條件是否為充要條件,就要去看出題人發的那個很長的證明了(我不會qwq)
#include<iostream>
#include<algorithm>
using namespace std;
long long gcd(long long a, long long b) {
if (a < b) swap(a, b);
while (b) {
a = a % b;
swap(a, b);
}
return a;
}
int main()
{
int T;
cin >> T;
long long n, k;
while (T--) {
cin >> n >> k;
long long gg;
while ((gg = gcd(n, k)) != 1) {
n /= gg;
}
if (n == 1) cout << "HUMAN\n";
else cout << "ROBOT\n";
}
return 0;
}

