A. 签到
简单C语言模拟题。
#include <stdio.h>
double a[5] = {0.01,0.05,0.15,0.3};
int n,p[5];
int main() {
scanf("%d", &n);
for(int i=0;i<4;i++)
p[i] = 386 * a[i];
if(n<=p[0]) printf("Winner!");
else if(n<=p[1]) printf("Au!");
else if(n<=p[2]) printf("Ag!");
else if(n<=p[3]) printf("Cu!");
else printf("Fe...");
return 0;
}
B. 切蛋糕
由于负鼠自己也要吃蛋糕,所以刚开始要 n+1 。
很容易看出当 n 是2的倍数的情况下,需要切n/2刀,否则至少切 n 刀。
#include <stdio.h>
int T,n;
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d",&n);
n++;
if(n==1) printf("0\n");
else if(n%2==0) printf("%d\n",n/2);
else printf("%d\n",n);
}
return 0;
}
C. 移动字符串
简单模拟。
注意,强行模拟 k 次必然超时。可以发现只有当前一个字母全都删完了后,才会删后一个字母,因此,我们可以将k次删除操作简化完成。
#include <cstdio>
#include <iostream>
int n, k, ds[30];
using namespace std;
int main() {
string s;
cin >> n >> k >> s;
for (int i = 0; i < s.length(); i++) ds[s[i] - 'a']++;
for (int i = 0; i < 26; i++)
if (k > ds[i])
k -= ds[i];
else
ds[i] = k, k = 0;
for (int i = 0; i < s.length(); i++)
ds[s[i] - 'a'] ? ds[s[i] - 'a']-- : putchar(s[i]);
return 0;
}
D. 卡布奇诺
数论+位运算。
每个数乘2或者除2,我们可以通过位运算枚举出每一个数 a i a_i ai 可以转变成的数, 以及变成这个数所需要的转换次数。
用 mp[i] 储存可以转变到的数 i i i 的次数。
因此最后我们只要找到最大的 i i i ,使得 m p [ i ] ≥ n mp[i] \geq n mp[i]≥n,然后枚举这个 i i i 的2的幂次方倍(必然满足条件),枚举得到所要求的最小值。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
template <class T>
void read(T &x) {
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,a[maxn],maxi;
int mp[maxn];
int cal_one(int a,int x) {
int res = 0;
while(x%a) {
a /= 2;
res++;
}
while(x>a) {
a *= 2;
res++;
}
return res;
}
int calc(int x) {
int res = 0;
for(int i=0;i<n;i++) {
res += cal_one(a[i],x);
}
return res;
}
int main() {
read(n);
for(int i=0;i<n;i++) {
read(a[i]);
ll f = a[i];
while(f) {
mp[f]++;
f /= 2;
}
}
int ans = 1e9;
for(int i=100000;i>=1;i--) {
if(mp[i]>=n) {
maxi = i;
break;
}
}
for(int i=maxi;i<=100000;i*=2) {
ans = min(ans,calc(i));
}
cout << ans << endl;
return 0;
}
E. 花钱
DFS + 简单组合数学。
显然这个图是一棵树,可以将图中任意一个节点作为树根建树。
那么可以将y作为树根,x则是树y的某一个节点,
设dep[i]为子树i的size(包括i本身),节点z为(y,x)路径上y的儿子节点
符合条件的对数为(y,x)路径两端的节点数相乘,即dep[x]*(dep[y]-dep[z]),用 n ∗ ( n − 1 ) n * (n-1) n∗(n−1)减去就可以得出答案。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 300005;
template <class T>
void read(T &x) {
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
ll n,s,t,cnt1,cnt2,dep[maxn],fa[maxn];
vector<int> e[maxn];
bool vis[maxn];
void dfs(int x) {
vis[x] = 1;
for(auto i:e[x]) {
if(!vis[i]) {
fa[i] = x;
dfs(i);
dep[x] += dep[i];
}
}
}
int main() {
read(n), read(s), read(t);
for(int i=0,x,y;i<n-1;i++) {
read(x), read(y);
e[x].push_back(y);
e[y].push_back(x);
}
for(int i=1;i<=n;i++) dep[i]=1;
dfs(s);
ll ans = n*(n-1);
for(int i=t;;i=fa[i]) {
if(fa[i]==s) {
cnt1 = dep[s]-dep[i];
break;
}
}
ans -= cnt1 * dep[t];
cout << ans << endl;
return 0;
}
F. 下棋
博弈论。
如果 CY
在 叉JJ
的左上方的话, CY
必胜;
同理,如果Cy
在 叉JJ
的右下方的话, CY
必败;
然后我们来考虑剩下的情况,由于 叉JJ
可以斜着走,因此,她回到
(
0
,
0
)
(0,0)
(0,0) 点至少需要移动
m
a
x
(
c
,
d
)
max(c,d)
max(c,d) 次,而CY
则需要移动
a
+
b
a+b
a+b 次。如果
a
+
b
>
m
a
x
(
c
,
d
)
a+b > max(c,d)
a+b>max(c,d), 那么叉JJ
必然可以以最短路提前到达终点或堵住 CY
的路, 此时 CY
必败, 否则 CY
必胜。
#include <bits/stdc++.h>
using namespace std;
int t,a,b,c,d;
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d%d%d",&a,&b,&c,&d);
if(a<=c&&b<=d) {puts("CY");}
else if(a>=c && b>=d) {puts("XJJ");}
else {
int f = a+b;
int g = max(c,d);
if(f<=g) puts("CY");
else puts("XJJ");
}
}
return 0;
}
G. 学Python
动态规划问题。
dp[i][j] 表示第i行的缩进有j个Tab时的合法方案数。
-
第i行为’f’,则第i+1行的缩进只能为j+1。
dp[i+1][j+1] = dp[i][j]
-
第i行为’s’,则第i+1行的缩进可以为[0,j]中的任意一种。
dp[i+1][j] += dp[i][0 to j]
最终结果即为 ∑ i = 0 n d p [ n ] [ i ] \sum_{i=0}^{n}dp[n][i] ∑i=0ndp[n][i]
#include <bits/stdc++.h>
using namespace std;
const int mo = 1e9 + 7;
int dp[5005][5005];
int main() {
int n;
cin >> n;
dp[1][0] = 1;
char s;
for (int i = 1; i <= n; i++) {
cin >> s;
if(s=='f') {
for(int j=0;j<n;j++)
dp[i+1][j+1] = dp[i][j];
} else {
int sum = 0;
for(int j=n-1;j>=0;j--) {
sum = (sum+dp[i][j]) % mo;
dp[i+1][j] = sum;
}
}
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum = (sum + dp[n][i]) % mo;
}
cout << sum << endl;
return 0;
}
H. Sort the String
线段树 + 计数排序。
由于字母只有26个,所以在给区间[l,r]排序的时候,只需要统计出每个字母的数量,然后再排序。
底下问题就来到了怎么统计数量以及更新数量。我们可以通过建立26棵线段树,每棵线段树维护一个字母在一段区间上的数量。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n,q;
char s[maxn];
struct SegmentTree {
int t[maxn*4], lazy[maxn*4];
void push_up(int p) {
t[p] = t[p<<1] + t[p<<1|1];
}
void push_down(int p,int l,int r) {
if(lazy[p]!=-1) {
int mid = (l+r) / 2;
t[p<<1] = (mid-l+1) * lazy[p];
t[p<<1|1] = (r-mid) * lazy[p];
lazy[p<<1] = lazy[p<<1|1] = lazy[p];
lazy[p] = -1;
}
}
void build(int p,int l,int r,char c) {
t[p] = 0, lazy[p] = -1;
if(l==r) {
t[p] = (s[l]==c);
return;
}
int mid = (l+r) / 2;
build(p<<1,l,mid,c);
build(p<<1|1,mid+1,r,c);
push_up(p);
}
// [L,R] 为查询的区间, [l,r] 为当前区间
void update(int p,int l,int r,int L,int R,int val) {
if(L<=l && R>=r) {
t[p] = (r-l+1) * val;
lazy[p] = val;
return;
}
push_down(p,l,r);
int mid = (l+r) / 2;
if(L<=mid) update(p<<1,l,mid,L,R,val);
if(R>mid) update(p<<1|1,mid+1,r,L,R,val);
push_up(p);
}
int query(int p,int l,int r,int L,int R) {
int res = 0;
if(L<=l && R>=r) return t[p];
push_down(p,l,r);
int mid = (l+r) / 2;
if(L<=mid) res += query(p<<1,l,mid,L,R);
if(R>mid) res += query(p<<1|1,mid+1,r,L,R);
push_up(p);
return res;
}
void print(int p,int l,int r,char c) {
if(l==r) {
if(t[p]) s[l] = c;
return;
}
push_down(p,l,r);
int mid = (l+r) / 2;
if(l<=mid) print(p<<1,l,mid,c);
if(r>mid) print(p<<1|1,mid+1,r,c);
push_up(p);
}
}tr[30];
int cnt[30];
int main() {
scanf("%d%d%s",&n,&q,s+1);
for(int i=0;i<26;i++)
tr[i].build(1,1,n,i+'a');
for(int i=0,l,r,op;i<q;i++) {
scanf("%d%d%d",&l,&r,&op);
memset(cnt,0,sizeof(cnt));
for(int i=0;i<26;i++)
cnt[i] = tr[i].query(1,1,n,l,r);
for(int i=0;i<26;i++)
tr[i].update(1,1,n,l,r,0);
if(!op) {
for(int i=25;i>=0;i--) {
if(!cnt[i]) continue;
tr[i].update(1,1,n,l,l+cnt[i]-1,1);
l += cnt[i];
}
} else {
for (int i=0;i<26;i++) {
if(!cnt[i]) continue;
tr[i].update(1,1,n,l,l+cnt[i]-1,1);
l += cnt[i];
}
}
}
for(int i=0;i<26;i++)
tr[i].print(1,1,n,i+'a');
for(int i=1;i<=n;i++) cout << s[i];
return 0;
}
I. BJ的猫饼
首先我们注意到, v i v_i vi 与 y i y_i yi 都是 2 2 2 的幂次,因此, ( ∏ v i ) ∗ ( ∏ y i ) (∏v_i) * (∏y_i) (∏vi)∗(∏yi) 等价于 2 ∑ log 2 v i + ∑ log 2 y i 2^{\sum \log_{2} v_i+\sum \log_{2} y_i} 2∑log2vi+∑log2yi,因此,我们仅需要计算 ∑ log 2 v i + ∑ log 2 y i \sum \log_{2} v_i+\sum \log_{2} y_i ∑log2vi+∑log2yi 的最大值即可
以下部分需要读者掌握初步的网络流以及费用流知识,若从未接触过,请首先出门左转 Google
好啦相信聪明的读者已经学会了网络流和费用流。
本题套用最小费用最大流模板,需要注意的是,本题实际上求的是最大费用最大流,因此给费用加负号再求最小费用即可。求出来的最小费用再加一个负号就是原来的最大价值之和。
我们需要的点有:源点,汇点,超级汇点,
n
n
n 个点表示
n
n
n 种普通猫饼,
m
m
m 个点表示
m
m
m 种超级猫饼。
从超级源点向每个普通猫饼建边,容量为
a
i
ai
ai,费用为
0
0
0。普通猫饼向汇点建边,容量为
c
i
ci
ci,费用为
−
log
2
(
v
i
)
-\log_2(v_i)
−log2(vi)。再从每种可以制成超级猫饼的普通猫饼,向相对应的超级猫饼建边,容量为
I
N
F
INF
INF,费用为
0
0
0。这些超级猫饼再向汇点建边,容量为
c
i
′
c'_i
ci′,费用为
−
log
2
(
y
i
)
-\log_2(y_i)
−log2(yi)。
最后,汇点向超级汇点建边,容量为
c
c
c,费用为
0
0
0。
#include <bits/stdc++.h>
const int maxn= 2e3+50;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
using namespace std;
struct edge{
int to, cap, cost, rev;
};
vector<edge>G[maxn];
int dis[maxn];
int pre[maxn], prid[maxn];
int h[maxn];
void init() {
for (int i = 0; i < maxn; ++i)
G[i].clear();
memset(pre, 0, sizeof(pre));
memset(prid, 0, sizeof(prid));
memset(h, 0, sizeof(h));
}
void add_edge(int from, int to, int cap, int cost) {
G[from].push_back({to, cap, cost, G[to].size()});
G[to].push_back({from, 0, -cost, G[from].size()-1});
}
void dijkstra(int s) {
struct point_dis {
int point;
int val;
bool operator < (const point_dis &pd) const {
return val > pd.val;
}
};
fill(dis, dis+maxn, INF);
dis[s] = 0;
priority_queue<point_dis>q;
q.push({s, dis[s]});
while (!q.empty()) {
int now = q.top().point;
q.pop();
for (int i = 0; i < G[now].size(); ++i) {
edge &e = G[now][i];
if (e.cap > 0 && dis[e.to] > dis[now] + e.cost + h[now] - h[e.to]) {
dis[e.to] = dis[now] + e.cost + h[now] - h[e.to];
pre[e.to] = now;
prid[e.to] = i;
q.push({e.to, dis[e.to]});
}
}
}
}
pair<int, int> min_cost_max_flow(int s, int t) {
int flow = 0;
int cost = 0;
while (true) {
dijkstra(s);
if (dis[t] == INF)
return {flow, cost};
for (int i = 0; i < maxn; ++i) {
h[i] += dis[i];
}
int d = INF;
for (int v = t; v != s; v = pre[v]) {
d = min(d, G[pre[v]][prid[v]].cap);
}
flow += d;
cost += d*h[t];
for (int v = t; v != s; v = pre[v]) {
G[pre[v]][prid[v]].cap -= d;
G[v][G[pre[v]][prid[v]].rev].cap += d;
}
}
}
long long quick_pow(int t) {
long long res = 1;
long long base = 2;
while (t) {
if (t & 1)
res = (res*base) % mod;
base = (base*base) % mod;
t >>= 1;
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
int n, m, c;
while (cin >> n >> m >> c) {
init();
int start_point = 0, meeting_point = 2048, end_point = 2049;
for (int i = 1; i <= n; ++i) {
int amount;
cin >> amount;
add_edge(start_point, i, amount, 0);
}
for (int i = 1; i <= n; ++i) {
int value, volume;
cin >> value >> volume;
value = log2(value);
add_edge(i, meeting_point, volume, -value);
}
for (int i = 1; i <= m; ++i) {
int k;
cin >> k;
while (k--) {
int id;
cin >> id;
add_edge(id, 1e3+i, INF, 0);
}
int value, volume;
cin >> value >> volume;
value = log2(value);
add_edge(1e3+i, meeting_point, volume, -value);
}
add_edge(meeting_point, end_point, c, 0);
pair<int, int>ans = min_cost_max_flow(start_point, end_point);
cout << quick_pow(-ans.second) << endl;
}
return 0;
}