A.辛苦的志愿者
如果m个小姐姐可以完成发气球任务,那么m+1个小姐姐也一定可以完成发气球任务。
二分+优先队列(也可以不用优先队列,直接用数组模拟一个普通队列往后插入就行了)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
#define mp make_pair
#define fi first
#define se second
#define ll long long
int n, m, t, d, ans = 0;
pair<ll,ll> p[maxn];
ll angry[maxn];
bool check(int mid){
priority_queue <ll,vector<ll>,greater<ll> > q;
if(!q.empty()) q.pop();
memset(angry,0,sizeof angry);
int now = mid;
bool flag = 1;
for(int i = 1;i <= n; i++){
if(now)
now--,q.push(p[i].fi + t);
else{
auto it = q.top();
q.pop();
if(it <= p[i].fi)
q.push(p[i].fi + t);
else{
angry[p[i].se] += it - p[i].fi;
q.push(it + t);
if(angry[p[i].se] >= d) flag = 0;
}
}
}
return flag;
}
int main(){
int a,b;
cin >> n >> m >> t >> d;
for(int i = 1; i <= n; i++){
cin >> a >> b;
p[i] = mp(a,b);
}
sort(p + 1, p + 1 + n);
int l = 1, r = n + 1;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans;
}
(弟弟标程是用的优先队列)
B. 北境之都
可以三分!三分比较好想。
但是弟弟标程用的前缀和。枚举最终房间高度,即最低为a,最高为a+k。然后通过前缀和的处理,在logn的时间内完成对答案的计算,并更新ans。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
#define ll long long
ll co[maxn],dqs2[maxn],dqs1[maxn], ans = 1e18;
ll n,k;
ll check(ll a){
ll tmp = 0;
ll low = lower_bound(co+1,co+1+n,a) - co - 1;
ll high = upper_bound(co+1,co+1+n,a+k) - co - 1;
tmp += low * a * a + dqs2[low] - 2 * a * dqs1[low];
tmp += (n - high) * (a + k) * (a + k) + dqs2[n] - dqs2[high] - 2 * (a + k) * (dqs1[n] - dqs1[high]);
return tmp;
}
int main(){
cin >> n >> k;
for(int i = 1; i <= n; i++)
cin >> co[i];
sort(co+1,co+1+n);
for(int i = 1; i <= n; i++)
dqs2[i] = dqs2[i-1] + co[i] * co[i], dqs1[i] = dqs1[i-1] + co[i];
for(int i = 1; i + k <= maxn - 5; i++)
ans = min(ans,check(i));
cout << ans;
}
C 景字棋
比较毒瘤的题。。大模拟,要考虑的情况很多。但是已经下了六步,降低了难度,可以直接判断双方是否能一步胜利(即A下一步,B再下一步能否结束),不能的话就是平局。
就是不合法比较难判,比如黑棋下三步已经胜利,白棋下了三步,这当然是不合法的。因为黑棋先下,三步胜利之后就结束了。
正确的出题方式应该是只判断是否合法。。再判断输赢真的毒瘤,我接锅。
D 今年寒假不AC
弟弟标程非用N²做,实际可以直接记录当前状态线性递推。
#include <bits/stdc++.h>
using namespace std;
long long n, w2[10], w3[10], a[2005], dp[2005], num[10][2005];
char s[2005];
int cal(int x, int y){
if(y < 3) return 0;
if(w2[x] * 3 >= w3[x]) return (y / 3) * w2[x];
return w3[x] * (y / 9) + w2[x] * ((y % 9) / 3);
}
int main(){
scanf("%lld", &n);
for(int i = 1; i <= 5; i++) scanf("%lld", &w2[i]);
for(int i = 1; i <= 5; i++) scanf("%lld", &w3[i]);
scanf("%s", s + 1);
for(int i = 1; i <= n; i++) a[i] = (s[i] - 'A') + 1;
for(int j = 1; j <= 5; j++) for(int i = 1; i <= n; i++) num[j][i] = num[j][i - 1] + (a[i] == j);
for(int i = 1; i <= n; i++) for(int j = 1; j <= i; j++) if(a[j] == a[i]){
dp[i] = max(dp[i], dp[j - 1] + cal(a[i], num[a[i]][i] - num[a[i]][j - 1]));
}
printf("%lld", dp[n]);
return 0;
}
于是我选用了现场赛中NWPU的哥哥代码作为标程。。
E. NE的挑战Ⅱ
带删除的并查集,对于删除操作,新开一个结点映射被删除的点即可。
#include <bits/stdc++.h>
using namespace std;
#define N 4000005
int n,m,fa[N],to[N],o,x,y,tot,a[N],b[N],cnt,gg[N],sz[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void mer(int x,int y){int a=find(to[x]),b=find(to[y]);if(a==b) return;fa[b]=a;sz[a]+=sz[b];sz[b]=0;}
void upd(int x,int y){sz[find(to[x])]--;to[x]=++cnt;sz[cnt]=1;fa[cnt]=cnt;mer(x,y);}
int main(){
scanf("%d%d",&n,&m);cnt=n;
for(int i=1;i<=n;i++) fa[i]=i,to[i]=i,sz[i]=1;
for(int i=1;i<=m;i++){
scanf("%d%d",&o,&x);if(o!=3) scanf("%d",&y);
if(o==5){a[++tot]=x,b[tot]=y;continue;}
if(o==1) mer(x,y);
if(o==2) upd(x,y);
if(o==3) printf("%d\n",sz[find(to[x])]-1);
if(o==4) puts(find(to[x])==find(to[y])?"Yes":"No");
}
for(int i=1;i<=tot;i++) if(find(to[a[i]])==find(to[b[i]])) gg[find(to[a[i]])]=1;
int mx=-1;
for(int i=1;i<=n;i++)
if(!gg[find(to[i])]) mx=max(mx,sz[find(to[i])]);
printf("%d\n",mx);
}
为了减少博客长度,选了一个压行怪的代码。
不过可读性还可以,毕竟上面都是并查集板子,压行就压行吧。。
F. 三生三世
递归回溯+组合数学
枚举这一位填哪个字母,假如这个字母比原串的字典序大,返回0,比原串字典序小,则返回其余字母的排列数。如果相等,再继续往后dfs。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#pragma comment(linker, "/STACK:102400000,102400000")
const ll mod = 1e9 + 7;
const int maxn = 2e5 + 5;
string str1,str2,str;
int num[30], n;
ll F[maxn], Finv[maxn], inv[maxn];//F是阶乘,Finv是逆元的阶乘
void init(){
inv[1] = 1;
for(int i = 2; i < maxn; i ++){
inv[i] =(mod - mod / i) * 1ll * inv[mod % i] % mod;
}
F[0] = Finv[0] = 1;
for(int i = 1; i < maxn; i ++){
F[i] = F[i-1] * 1ll * i % mod;
Finv[i] = Finv[i-1] * 1ll * inv[i] % mod;
}
}
ll comb(int n, int m){//comb(n, m)就是C(n, m)
if(m < 0 || m > n) return 0;
return F[n] * 1ll * Finv[n - m] % mod * Finv[m] % mod;
}
ll dfs(int len, int now){
if(len == n - 1) return 0;//如果走到字符串最后一位,返回
if(len != -1 && now < str[len] - 'A'){//如果当前位置已经比字符串字典序小,后面就可以任意摆放
//用组合数学的方法算出后面可以摆放出几种
int need = n - len - 1;
ll tmp = 1;
for(int i = 0; i < 26; i++)
if(num[i])
tmp = (tmp * comb(need, num[i])) % mod, need -= num[i];
return tmp;
}
ll tmp = 0;
for(int i = 0; i < 26; i++){
if(i <= str[len+1] - 'A' && num[i]){
num[i]--;
tmp = (tmp + dfs(len + 1, i)) % mod;//尝试选择i字符,进入下一层递归
num[i]++;//回溯
}
}
return tmp;
}
ll solve(string strk){
memset(num,0,sizeof num);
str = strk;
for(int i = 0; i < n; i++)
num[str[i]-'A']++;//统计每种字符出现次数
return dfs(-1,30);
}
int main(){
init();
cin >> n;
cin >> str1 >> str2;
cout << (solve(str1) - solve(str2) + mod) % mod;
}