CSP-S2020解題報告(待完成!)
\(CSP-S2020\)合集
A. 儒略日
這道題模擬能有\(80pts\),可是考場上我不看題,花了將近三個小時敲大模擬\(40pts\)。
這道題實際上模擬的話可以分為公元前、1582.10.4、1582.10.5~1600.12.31,1600.12.31以後,為什麼要分到$$1600.12.31$$為結點呢?後面每四百年蹦一次方便。
當然,我們也可以通過觀察樣例求解:
考慮到\(P=3000000\)天時恰好是\(3501.8.15\),再往後蹦\(400\)年要\(T\)即\(146097\)天。暴力預處理這些天數。然後小於等於\(C=3146097\)的天數直接輸出,大於的年月用\(C+r\% T\)天數輸出,年份\((r/T)\)
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #include<queue> #include<ctime> #include<set> #include<map> #define CLR(x, y) memset(x,y,sizeof(x)) #define FOR(i, x, y) for(register int i=x;i<=y;++i) #define ROF(i, x, y) for(register int i=x;i>=y;--i) using namespace std; const int C = 3146097, T = 146097, E = 3000000; int D[C + 7], M[C + 7], Y[C + 7]; void init() { int day = 1, mon = 1, year = -4713; bool op = true; D[0] = 1, M[0] = 1, Y[0] = -4713; FOR(i, 1, C) { ++ day; if(mon == 2) if((op && (day == 30)) || (op == false && (day == 29))) ++ mon, day = 1; if(mon == 1 || mon == 3 || mon == 5 || mon == 7 || mon == 8 || mon == 10 || mon == 12) if(day == 32) ++ mon, day = 1; if(mon == 4 || mon == 6 || mon == 9 || mon == 11) if(day == 31) ++ mon, day = 1; if(mon == 13) ++ year, mon = 1; if(year == 0) year = 1; if(year == 1582 && mon == 10 && day == 5) day = 15; D[i] = day, M[i] = mon, Y[i] = year; if(year < 0) op = (-year) % 4 == 1 ? true : false; else { if(year < 1583) op = year % 4 == 0 ? true : false; else op = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)? true:false; } } return; } long long r; int main() { int q; scanf("%d", &q); init(); while(q --) { scanf("%lld", &r); if(r <= C) { printf("%d %d %d", D[r], M[r], abs(Y[r])); if(Y[r] < 0) printf(" BC\n"); else puts(""); } else { int tmp = E + (r - E) % T; printf("%d %d %d\n", D[tmp], M[tmp], Y[tmp] + (r - E) / T * 400); } } return 0; }
B. 動物園
無語,竟然忘記特判了。\(1\)移\(64\)位就誰也救不了我了。
#include<algorithm> #include<iostream> #include<cstring> #include<vector> #include<string> #include<cstdio> #include<cmath> #include<bitset> #include<queue> #include<map> #include<set> #define ull unsigned long long using namespace std; const int N = 1000000 + 5; bitset <100000000 + 5> table; struct query { int p, q; } Q[N]; int n, m, c, k; ull a[N]; bool book[71] = {}; inline void read(ull &x) { bool mark = false; char ch = getchar(); for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true; for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; if(mark) x = -x; return; } int calc(ull x) { int res = 0; while(x) { if(x & 1) ++ res; x >>= 1; } return res; } int main() { table.reset(); scanf("%d %d %d %d", &n, &m, &c, &k); ull P = 0; for(int i = 1; i <= n; ++ i) read(a[i]); for(int i = 1; i <= n; ++ i) P |= a[i]; for(int i = 0; i < m; ++ i) { scanf("%d %d", &Q[i].p, &Q[i].q); if((P >> Q[i].p) & 1) table[Q[i].q] = true; } ull res = 0; for(int i = 0; i < m; ++ i) { if(table[Q[i].q]) res |= 1ll << Q[i].p; else book[Q[i].p] = true; } bool valid = false; for(int i = 0; i < k; ++ i) { if(!book[i]) { res |= 1ll << i; } else valid = true; } if(valid || k < 64) { int cnt = calc(res); printf("%lld\n", (1ll << cnt) - n); } else printf("18446744073709551616\n"); return 0; }
C. 函式呼叫
考場上不看題,暴力分都沒拿到。
這道題模擬、暴力\(20pts\)。更優地,我們甚至可以將乘法累積到變數,加法乘該積數逆用於優化常數。
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#define CLR(x, y) memset(x,y,sizeof(x))
#define FOR(i, x, y) for(register int i=x;i<=y;++i)
#define ROF(i, x, y) for(register int i=x;i>=y;--i)
#define pii pair<int,int>
using namespace std;
const int N = 200000 + 5, mod = 998244353;
struct Query
{
int t, p, v, c;
vector <int> g;
} q[N];
int n, m, Q, a[N], f[N];
template <typename T> void read(T &x)
{
bool mark = false;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
if(mark) x = -x;
return;
}
void work(int p)
{
if(q[p].t == 1) a[q[p].p] = (a[q[p].p] + q[p].v) % mod;
else if(q[p].t == 2) FOR(i, 1, n) a[i] = 1ll * a[i] * q[p].v % mod;
else FOR(i, 0, q[p].g.size() - 1) work(q[p].g[i]);
return;
}
int main()
{
read(n);
FOR(i, 1, n) read(a[i]);
read(m);
FOR(i, 1, m)
{
read(q[i].t);
switch(q[i].t)
{
case 1:
{
read(q[i].p), read(q[i].v);
break;
}
case 2:
{
read(q[i].v);
break;
}
case 3:
{
read(q[i].c);
FOR(j, 1, q[i].c)
{
int tmp;
read(tmp);
q[i].g.push_back(tmp);
}
break;
}
}
}
read(Q);
FOR(i, 1, Q) read(f[i]);
FOR(i, 1, Q) work(f[i]);
FOR(i, 1, n) printf("%d ", a[i]);
puts("");
return 0;
}
假設操作序列中只有加法或乘法操作,那麼呼叫順序毫無影響。
對於加法操作,我們只需要把每個函式對序列的影響記錄下來,計算每一位上的數的變數。具體來說,我們可以根據呼叫關係建立一張DAG,按照拓撲序計算出每一個加法操作被呼叫次數,對於每一個加法操作函式,可以記錄它更新的位置,統計時將該位置上的數加上總次數即可。
乘法操作雖則如出一轍,但我們對於該問題,有不一樣的解法。考慮計算每一個函式所產生的乘積貢獻值,將所有呼叫的函式乘積貢獻值(即\(dp1[i]\))統計一下即可。
我們將問題轉化:對於每一個數\(a_i\),先不考慮加的數對它的影響,它最終的答案就是總乘積乘該數。那麼,該問題變為了對於每一個加法操作對整個序列的影響統計,可以考慮模擬的整個過程。
該過程中,會發現,設第\(f_i\)個為加法操作,那麼,對於後面的所有的\(f_j(j>i)\),它們的乘法貢獻一定會作用於第\(f_i\)個操作的值,最後統計即可。也就是說\(val*mul\)。
為了能夠統計後面的乘積,使其能夠對前面有影響,我們倒序進行。
時間複雜度降低為\(O(Q*m)\)。
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#define CLR(x, y) memset(x,y,sizeof(x))
#define FOR(i, x, y) for(register int i=x;i<=y;++i)
#define ROF(i, x, y) for(register int i=x;i>=y;--i)
using namespace std;
const int N = 100000 + 5, M = 100000 + 5, mod = 998244353;
typedef long long LL;
vector <int> G[M];
int n, m, f[N], p[M], type[M];
LL a[N], mul = 1, v[M], add[N];
void dfs(int u)
{
if(type[u] == 1)
{
add[p[u]] = (add[p[u]] + 1ll * v[u] * mul % mod) % mod;
return;
}
if(type[u] == 2)
{
mul = 1ll * mul * v[u] % mod;
return;
}
int v;
for(int i = G[u].size() - 1; i >= 0; -- i)
{
v = G[u][i];
dfs(v);
}
return;
}
template <typename T> void read(T &x)
{
bool mark = false;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
if(mark) x = -x;
return;
}
int main()
{
read(n);
FOR(i, 1, n) read(a[i]);
read(m);
FOR(i, 1, m) G[i].clear();
FOR(i, 1, m)
{
read(type[i]);
switch(type[i])
{
case 1:
read(p[i]), read(v[i]);
break;
case 2:
read(v[i]);
break;
case 3:
int tmp;
read(tmp);
G[i].resize(tmp);
FOR(j, 0, tmp - 1) read(G[i][j]);
break;
default: break;
}
}
int T;
read(T);
FOR(i, 1, T) read(f[i]);
CLR(add, 0);
int u;
ROF(i, T, 1)
{
u = f[i];
if(type[u] == 1) add[p[u]] = (add[p[u]] + v[u] * mul) % mod;
else if(type[u] == 2) mul = 1ll * mul * v[u] % mod;
else dfs(u);
}
FOR(i, 1, n) printf("%lld ", (1ll * a[i] * mul % mod + add[i]) % mod);
puts("");
return 0;
}
這樣做並不是最好的。
考慮:一個加法被乘\(k\)次,相當於該函式被呼叫\(k\)次。
而函式的“呼叫函式”被呼叫的次數需要累加到該函式內。
定義一個數組\(dp[u]\)代表結點被呼叫了幾次。
如果該節點是個乘法呼叫,不用理它,因為乘法我們已經計算完了;如果是一個“呼叫函式”那麼,接下來的步驟,就是來解決內部的函式呼叫的統計;如果是一個加法呼叫,可以累加到\(add[i]\)第\(i\)位影響。
接下來,我們統計所有在“呼叫函式”中被呼叫的函式呼叫次數。
拓撲排序。
不過,在TOP過程中,要隨時記錄乘積,更新呼叫次數;另外,對於一個“呼叫函式”的呼叫順序,可以倒序求解。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#define CLR(x, y) memset(x,y,sizeof(x))
#define FOR(i, x, y) for(register int i=x;i<=y;++i)
#define ROF(i, x, y) for(register int i=x;i>=y;--i)
using namespace std;
const int N = 100000 + 5, M = 100000 + 5, mod = 998244353;
typedef long long LL;
template <typename T> void read(T &x)
{
bool mark = false;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true;
for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
if(mark) x = -x;
return;
}
vector <int> G[M];
queue <int> Q;
int n, m, f[N], p[M], type[M], deg[M];
LL a[N], mul = 1, v[M], dp1[M], dp[M], add[N];
void dfs(int u)
{
if(dp1[u] != -1) return;
if(type[u] == 1) dp1[u] = 1;
else if(type[u] == 2) dp1[u] = v[u];
else
{
dp1[u] = 1;
int x;
for(int i = 0; i < G[u].size(); ++ i)
{
x = G[u][i];
dfs(x);
dp1[u] = 1ll * dp1[u] * dp1[x] % mod;
}
}
}
int main()
{
read(n);
FOR(i, 1, n) read(a[i]);
read(m);
CLR(deg, 0);
FOR(i, 1, m) G[i].clear();
FOR(i, 1, m)
{
read(type[i]);
switch(type[i])
{
case 1:
read(p[i]), read(v[i]);
break;
case 2:
read(v[i]);
break;
case 3:
int tmp;
read(tmp);
G[i].resize(tmp);
FOR(j, 0, tmp - 1)
{
read(G[i][j]);
++ deg[G[i][j]];
}
break;
default: break;
}
}
CLR(dp1, -1), CLR(dp, 0);
FOR(i, 1, m) if(!deg[i]) dfs(i);
int u, T;
LL w;
read(T);
FOR(i, 1, T) read(f[i]);
ROF(i, T, 1)
{
u = f[i];
if(type[u] == 1) dp[u] = (dp[u] + mul) % mod;
else if(type[u] == 2) mul = 1ll * mul * dp1[u] % mod;
else dp[u] = (dp[u] + mul) % mod, mul = 1ll * mul * dp1[u] % mod;
}
CLR(add, 0);
while(Q.size()) Q.pop();
FOR(i, 1, m) if(!deg[i]) Q.push(i);
while(Q.size())
{
u = Q.front();
Q.pop();
if(type[u] == 1) add[p[u]] = (add[p[u]] + dp[u] * v[u]) % mod;
w = dp[u];
reverse(G[u].begin(), G[u].end());
for(int i = 0; i < G[u].size(); ++ i)
{
int x = G[u][i];
-- deg[x];
dp[x] = (dp[x] + w) % mod;
w = 1ll * w * dp1[x] % mod;
if(!deg[x]) Q.push(x);
}
}
FOR(i, 1, n) printf("%lld ", (1ll * a[i] * mul % mod + add[i]) % mod);
puts("");
return 0;
}