NWERC 2020 部分题解
A. Atomic Energy
题意
完全背包,物品的体积是\(1,2...n\) 价值是\(a_1,a_2...a_n\)
给出\(q\)次询问,每次询问体积恰为\(k\)的背包的价值是多少
分析
发现背包的体积很大,物品的体积很小,能够明显地感觉到应该大范围贪心。
具体到哪个范围呢?考虑所有物品中性价比最高的物品体积\(m\),假设最后背包中的物品由体积为\(b_1,b_2...\)组成,可以得到结论若\(size(b) \geq m - 1\),可以把其中的物品用\(m\)替换为性价比最高的物品。这是因为大小为\(n\)的数组,总能找出子集使得和为\(n - 1\)(简证:对前缀和模n - 1,可以得到n个数至少有一对相同)。于是只需要做背包时把体积上限定为\(n \times (m - 1)\)即可
代码
这是赛时代码,事实上上限可以进一步逼近
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, q;
const int N = 110, M = 5e5 + 10;
ll a[N];
ll f[M];
int main(){
memset(f, 0x3f, sizeof f);
scanf("%d%d",&n,&q);
double mi = 1e9 + 1;
int pos = -1;
for (int i = 1; i <= n; i++) {
scanf("%lld",&a[i]);
double x = 1.*a[i] / i;
if (x < mi) {
mi = x;
pos = i;
}
}
assert(pos != -1);
for (int i = 1; i <= n; i++) {
f[i] = a[i];
}
constexpr int lim = 5e5;
for (int i = n + 1; i <= lim; i++) {
for (int j = 1; j <= n; j++) {
f[i] = min(f[i], f[i - j] + a[j]);
}
}
while (q--) {
int k;
scanf("%d",&k);
if (k <= lim) printf("%lld\n",f[k]);
else {
int y = k - lim;
int d = (y + pos - 1) / pos;
long long ans = (long long)d * a[pos];
k -= d * pos;
ans += f[k];
printf("%lld\n",ans);
}
}
return 0;
}
C.Contest Struggles
水题
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//d * n = s * k + remain
inline int rd(){
int x;
scanf("%d",&x);
return x;
}
int main(){
int n = rd();
int k = rd();
int d = rd();
int s = rd();
double ans = (d * n - s * k) * 1.0 / (n - k);
if(ans >= 0 && ans <= 100)
printf("%lf",(d * n - s * k) * 1.0 / (n - k));
else puts("impossible");
}
D.Dragon Balls
题意
在2维平面的第一象限上(包括坐标轴)有\(n\)个点
你可以询问至多\(1000\)次,每次询问给出坐标\((x,y)\),每次会返回离该点的最近的点的距离的平方
点的范围\(0 \leq x \leq 10^6,0\leq y \leq 10^6\)
点的个数\(1 \leq n\leq 7\)
返回的距离\(0 \leq d \leq2\times 10^{12}\)
分析
尝试每次找原点,这样每次for圆弧上的整点,即可得到点的坐标
这样的前提是圆弧上的点不会很多,打表得到\(r\)较大时得到的点的个数不会很多
注意日经问题:开根号可能会有误差,不妨在map里预处理出所有开方数,每次check一下即可
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//d * n = s * k + remain
inline ll rd(){
ll x;
scanf("%lld",&x);
return x;
}
ll get(int x,int y){
printf("%d %d\n",x,y);
fflush(stdout);
ll g = rd();
return g;
}
int main(){
int n = rd();
int cnt = 0;
map<ll,bool> mp;
for(int i = 0;i <= (int)1e6;i++)
mp[(ll)i * i] = 1;
while(cnt < n){
ll d = get(0,0);
if(!d) {
cnt++;
continue;
}
vector<pair<int,int>> v;
for(ll x = 0;x * x <= d;x++){
ll y = sqrt(d - x * x);
if(!mp.count(d - x * x)) continue;
v.push_back(make_pair(x,y));
}
for(int i = 0;i < v.size();i++){
ll d = get(v[i].first,v[i].second);
if(!d) {
cnt++;
break;
}
}
}
}
E.Endgame
题意
在\(n \times n\)的棋盘上两人进行移动。每次两人可以选择如下操作之一:1.进行两次给定的移动。2.移动到任意没有棋子的位置3.不移动
共有\(n\)个给定移动,负数表示向左或者向上,不能移动到界外
输出Alice或者Bob或者平局,若平局输出某个情况下的Alice的位置
\(2 \leq n \leq 10^5\)
分析
考虑给定的移动只有\(n\)次移动具有什么性质,得到每次只能移动到至多\(n^2/2\)个位置
Alice如果不能在第一次就在两步内移动到Bob,那么Alice不会赢(由于有任意移动的操作)
所以我们主要考察初始状态即可,再结合\(n^2/2\)的性质,说明随机在棋盘上选定一个点,它在可以移动的位置的概率最多是\(1/2\),如果选\(k\),次,一次都没有选到不可达位置的概率是\((1/2)^k\) ,因此上随机化就行了(之前的题的思想)
已知棋盘上两点,如何判断能否在两点内可达。只须考虑\(meet \ in \ middle\)的思想就可以做到\(O(nlogn)\)
因此如果局面是\(tie\),就可以在\(100\)次左右随机到答案,否则输出Bob
代码
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define eps 1e-9
#define db long double
#define equals(a,b) fabs(a-b) < eps
using namespace std;
typedef long long ll;
inline ll rd(){
ll x;
scanf("%lld",&x);
return x;
}
const int maxn = 4e5 + 5;
mt19937 rnd(time(0));
map<pii,bool> mp;
pii op[maxn];
int n;
bool ok(int dp,int dv,int p,int v){
if(!dp && !dv) return 1;
if(mp[make_pair(dp,dv)]) return 1;
for(int i = 1;i <= n;i++){
int uu = p + op[i].fi;
int vv = v + op[i].se;
if(uu <= 0 || uu > n || vv <= 0 || vv > n) continue;
if(mp[make_pair(dp - op[i].fi,dv - op[i].se)]) return 1;
}
return 0;
}
int main(){
n = rd();
int stx,sty;
int edx,edy;
stx = rd(),sty = rd();
edx = rd(),edy = rd();
for(int i = 1;i <= n;i++){
int dx = rd();
int dy = rd();
auto p = make_pair(dx,dy);
mp[p] = 1;
op[i] = p;
}
if(ok(edx - stx,edy - sty,stx,sty)) {
puts("Alice wins");
return 0;
}
int times = 500;
while(times--){
int i = rnd() % n + 1,j = rnd() % n + 1;
if(!ok(i - edx,j - edy,edx,edy)) {
printf("tie %d %d\n",i,j);
return 0;
}
}
puts("Bob wins");
}
F.Flight Collision
题意
一维坐标上飞机具有\(x_i\)坐标和速度\(v_i\),其中速度是矢量
若两飞机相遇则会同时消失,问最后留下的飞机的编号
分析
观察到性质每次相撞的飞机总是前一时间相邻的飞机
每次当前局面下第一次相撞的飞机是距离除以相对速度最小的飞机
因此可以维护当前和后继的相遇时间,每次pop出来后删掉即可
维护前驱后继可以用链表,也可以用set(因为前驱后继一定有偏序关系)
实现起来可能有点麻烦,这里用pop需要处理一下之前留下的没用的结点,遇到是时候注意continue掉就好了
代码
#include<bits/stdc++.h>
#define pii pair<db,pair<int,int>>
#define fi first
#define se second
#define eps 1e-9
#define db long double
#define equals(a,b) fabs(a-b) < eps
using namespace std;
typedef long long ll;
//d * n = s * k + remain
inline ll rd(){
ll x;
scanf("%lld",&x);
return x;
}
const int maxn = 1e5 + 5;
int x[maxn],v[maxn];
priority_queue<pii> Q;
void add(int p,int q){
if(v[p] <= v[q]) return;
db T = (db)(x[p] - x[q]) / (db)(v[p] - v[q]);
Q.push(make_pair(T,make_pair(p,q)));
}
int main(){
set<int> s;
int n = rd();
for(int i = 1;i <= n;i++){
x[i] = rd();
v[i] = rd();
s.insert(i);
}
for(int i = 1;i < n;i++)
add(i,i + 1);
while(!Q.empty() && s.size()){
auto P = Q.top().se;
int i = P.fi;
int j = P.se;
Q.pop();
if(!s.count(j) || !s.count(i)) continue;
s.erase(i);
s.erase(j);
if(!s.size()) break;
auto it = s.lower_bound(i);
if(it == s.end() || it == s.begin()) continue;
int pre = *(--it);
int nxt = *s.lower_bound(j);
add(pre,nxt);
}
printf("%d\n",s.size());
for(auto it:s){
cout << it << ‘\n‘;
}
}
H.Hot Springs
题意
给定\(t_1,t_2...t_n\)重新排列使得\(|t‘_{i-1} - t‘_{i+1}| \leq |t‘_i - t‘_{i+1}|\)
分析
这种题图画出来就很容易发现该如何构造了
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//d * n = s * k + remain
inline int rd(){
int x;
scanf("%d",&x);
return x;
}
int main(){
int n = rd();
vector<int> v(n);
for(int i = 0;i < n;i++){
v[i] = rd();
}
sort(v.begin(),v.end());
if(n == 2) {
cout << v[0] << ‘ ‘ << v[1];
return 0;
}
if(n == 3) {
cout << v[1] << ‘ ‘ << v[0] << ‘ ‘ << v[2];
return 0;
}
vector<int> ans;
if(abs(v[0] - v[n - 2]) > abs(v[1] - v[n - 1])) {
ans.push_back(n - 1);
ans.push_back(0);
ans.push_back(n - 2);
int l = 1,r = n - 3;
for(int i = 0;i < n - 3;i++){
if(!(i & 1)) ans.push_back(l++);
else ans.push_back(r--);
}
}
else {
ans.push_back(0);
ans.push_back(n - 1);
ans.push_back(1);
int l = 2,r = n - 2;
for(int i = 0;i < n - 3;i++){
if(i & 1) ans.push_back(l++);
else ans.push_back(r--);
}
}
reverse(ans.begin(),ans.end());
for(auto it:ans){
cout << v[it] << ‘ ‘;
}
}
I.Island Tour
队友写的
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=405;
int d[maxn];
int n;
int t[4][maxn];
struct node{
int l,r;
}s[4][405][405];
vector<int>ab[405];
vector<int>ac[405];
vector<int>bc[405];
map<pair<int,int>,int>mp;
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&d[i]);
}
for(int j=1;j<=3;j++)
for(int i=1;i<=n;i++){
scanf("%d",&t[j][i]);
}
for(int person=1;person<=3;person++){
for(int i=1;i<=n;i++){
int now=1;
int cnt=0;
for(int j=i;j<=n;j++){
s[person][i][j]=node{now,now+t[person][j]-1};
now=now+t[person][j]-1;
now=now+d[j]+1;
}
for(int j=1;j<i;j++){
s[person][i][j]=node{now,now+t[person][j]-1};
now=now+t[person][j]-1;
now=now+d[j]+1;
}
}
/*
for(int i=1;i<=n;i++){
cout<<i<<endl;
for(int j=i;j<=n;j++){
cout<<s[person][i][j].l<<" "<<s[person][i][j].r<<"xxx";
}
for(int j=1;j<i;j++){
cout<<s[person][i][j].l<<" "<<s[person][i][j].r<<"xxx";
}
cout<<endl;
}
*/
}
// a b
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int kk=1;
for(int k=1;k<=n;k++){
if(!(s[1][i][k].l>s[2][j][k].r||s[2][j][k].l>s[1][i][k].r)){
kk=0;
}
}
if(kk)ab[i].push_back(j);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int kk=1;
for(int k=1;k<=n;k++){
if(!(s[1][i][k].l>s[3][j][k].r||s[3][j][k].l>s[1][i][k].r)){
kk=0;
}
}
if(kk)ac[i].push_back(j);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int kk=1;
for(int k=1;k<=n;k++){
if(!(s[2][i][k].l>s[3][j][k].r||s[3][j][k].l>s[2][i][k].r)){
kk=0;
}
}
if(kk)bc[i].push_back(j);
}
}
/*
for(int i=1;i<=n;i++){
for(auto x:ab[i]){
cout<<i<<" "<<x<<endl;
}
}
for(int i=1;i<=n;i++){
for(auto x:ac[i]){
cout<<i<<" "<<x<<endl;
}
}
for(int i=1;i<=n;i++){
for(auto x:bc[i]){
cout<<i<<" "<<x<<endl;
}
}
*/
for(int i=1;i<=n;i++){
for(auto x:bc[i]){
mp[{i,x}]=1;
}
}
for(int i=1;i<=n;i++){
for(auto x:ab[i]){
for(auto y:ac[i]){
if(mp[{x,y}]){
cout<<i<<" "<<x<<" "<<y<<endl;
return 0;
}
}
}
}
cout<<"impossible";
}
K.Keyboardd
水题
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//d * n = s * k + remain
inline int rd(){
int x;
scanf("%d",&x);
return x;
}
map<char,int>mp;
int main(){
string s,t;
getline(cin,s);
getline(cin,t);
int now=1;
int p=0;
for(int i=1;i<t.size();i++){
if(t[i]==t[i-1]){
now++;
}else{
int pp=0;
for(int j=p;j<s.size();j++){
if(s[j]==t[i-1])pp++;
else {
p=j;
break;
}
}
if(pp==now){
now=1;
}else{
if(mp[t[i-1]]!=1)cout<<t[i-1];
mp[t[i-1]]=1;
now=1;
}
}
}
if(now!=(s.size()-p)){
if(mp[s[p]]!=1)cout<<s[p];
}
}