Preface
這場由於是學長們出的題,在5.5就作為驗題隊伍VP了一遍
本來驗題場一般是三人三機火速開題的,但由於那天徐神沒帶電腦,索性就三人一機當作訓練來打
最後經典被隊友帶飛,4h8題後和NJU的WF銀牌隊只差一題,然後最後1h我衝刺H題失敗,恥辱下機吃花雕
A. Two's Company but Three's Trumpery
防AK的神秘圖論討論題,鑑定為先吃飯吧
B. Area of the Devil
唉幾何題,被徐神看一眼秒殺了,然後祁神上去隨便搓了下就過了
我們隊的寫法是咋拆分面積的說實話我也記不清了,反正我劃劃水就過了
#include<bits/stdc++.h>
using namespace std;
using LD = long double;
const LD PI = acos(-1.0L);
struct Pt{
LD x, y;
Pt operator-(const Pt &b)const{return Pt{x-b.x, y-b.y};};
Pt operator+(const Pt &b)const{return Pt{x+b.x, y+b.y};};
Pt operator*(const LD &b)const{return Pt{x*b, y*b};};
LD crs(const Pt &b)const{return x*b.y-y*b.x;}
};
LD cross(const Pt &p, const Pt &a, const Pt &b){
return (a-p).crs(b-p);
}
Pt pt_l_l(const Pt &p1, const Pt &v1, const Pt &p2, const Pt &v2){
return p1 + v1*(v2.crs(p1-p2)/v1.crs(v2));
}
Pt pt_seg_seg(const Pt &p1, const Pt &p2, const Pt &p3, const Pt &p4){
return pt_l_l(p1, p2-p1, p3, p4-p3);
}
int t, r, thi[5][2];
Pt pt[5][2];
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << setiosflags(ios::fixed) << setprecision(9);
cin >> t;
while (t--){
cin >> r;
for (int p=0; p<2; ++p){
for (int i=0; i<5; ++i){
cin >> thi[i][p];
pt[i][p] = Pt{r*cos(PI*thi[i][p]/180), r*sin(PI*thi[i][p]/180)};
}
}
LD ans = 0;
for (int i=0; i<5; ++i){
ans += 0.5L*pt[i][0].crs(pt[i][1]);
ans += 0.5L*pt[i][1].crs(pt[(i+1)%5][0]);
ans += 0.5L*(1.0L*r*r*(thi[i][1]-thi[i][0])/180*PI - pt[i][0].crs(pt[i][1]));
Pt p = pt_seg_seg(pt[i][1], pt[(i+2)%5][0], pt[(i+1)%5][1], pt[(i+3)%5][0]);
ans -= abs(0.5L*cross(p, pt[(i+1)%5][1], pt[(i+2)%5][0]));
}
cout << ans << '\n';
}
return 0;
}
C. Radio Direction Finding
徐神開場直接開的這個題,然後問候了出題人很久後找到一個分6種Case討論的做法
然而因為我根本不知道這題題意,又只能扔隊友程式碼了
#include <bits/stdc++.h>
int n;
inline int norm(int a) {
a -= 1;
a %= n;
if(a < 0) a += n;
return a + 1;
}
int query(int a) {
std::cout << "? " << norm(a) << std::endl;
int res; std::cin >> res;
return res;
}
void answer(int a, int b) {
std::cout << "! " << norm(a) << ' ' << norm(b) << std::endl;
return ;
}
void work() {
std::cin >> n;
int q1 = query(1), q2 = query(2);
if(q1 == q2) {
int l = 1, r = q1, mid;
while(l < r) {
int mid = (l + r + 1) >> 1;
if(query(1 + mid) == q1) l = mid;
else r = mid - 1;
}
l += 1;
if(q1 <= n / 2) {
answer(l - q1, l);
} else {
int d = n - q1;
int x = l + (n - d - d + 1) / 2;
int y = x + d;
answer(x, y);
}
} else
if(q1 - q2 == 1) {
int d = n - q1;
int x = 2 + (q2 - d) / 2;
int y = x + d;
answer(x, y);
} else
if(q1 - q2 == -1) {
int d = n - q2;
int x = 1 - (q1 - d) / 2;
int y = x - d;
answer(x, y);
} else
if(q1 - q2 == 2) {
int d = query(1 + q1 / 2);
int x = 1 + (q1 - d) / 2;
int y = x + d;
answer(x, y);
} else
if(q1 - q2 == -2) {
int d = query(2 - q2 / 2);
int x = 2 - (q2 - d) / 2;
int y = x - d;
answer(x, y);
}
return ;
}
int main() {
std::ios::sync_with_stdio(false);
int T; std::cin >> T; while(T--) work();
return 0;
}
D. City Bloxx
很有意思的構造題,話說為什麼祁神和徐神都玩過這個造房子的小遊戲但我完全沒有印象
比賽的時候其實已經搞出來了一個理論上可行的構造方法了,但由於一些細節問題(以及機子被我佔著寫shi了)所以沒來得及寫
祁神賽後好像把這題補掉了來著
E. Divide
據說有一個\(\log\)的優秀做法,但這個時間和空間限制不寫兩個\(\log\)真是可惜了
首先不難發現一個數操作\(\log\)次後就會變成\(0\),因此我們把每個位置上的數以及其操作過後所有能得到的數看作一個可重集
此時的詢問l r k
等價於詢問\([l,r]\)的所有可重集的並集中,排名第\(k+1\)大的數是什麼
可以直接用主席樹來大力維護上述問題,時空複雜度均為\(O(n\log^2 n)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,q,c[N],x,l,r,k,rt[N];
class Segment_Tree
{
private:
struct segment
{
int ls,rs,sz;
}node[N*400]; int tot;
public:
#define TN CI l=1,CI r=100000
#define LS l,mid
#define RS mid+1,r
inline void insert(CI lst,int& now,CI pos,TN)
{
node[now=++tot]=node[lst]; ++node[now].sz;
if (l==r) return; int mid=l+r>>1;
if (pos<=mid) insert(node[lst].ls,node[now].ls,pos,LS);
else insert(node[lst].rs,node[now].rs,pos,RS);
}
inline int query(CI x,CI y,CI rk,TN)
{
if (l==r) return l; int mid=l+r>>1;
int szr=node[node[y].rs].sz-node[node[x].rs].sz;
if (rk<=szr) return query(node[x].rs,node[y].rs,rk,RS);
else return query(node[x].ls,node[y].ls,rk-szr,LS);
}
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
RI i; for (scanf("%d%d",&n,&q),i=1;i<=n;++i)
{
scanf("%d",&x); if (x==0) { rt[i]=rt[i-1]; c[i]=c[i-1]; continue; }
SEG.insert(rt[i-1],rt[i],x); c[i]=1; x>>=1; int lst=rt[i];
while (x) SEG.insert(lst,rt[i],x),lst=rt[i],++c[i],x>>=1;
c[i]+=c[i-1];
}
for (i=1;i<=q;++i)
{
scanf("%d%d%d",&l,&r,&k); ++k;
if (k>c[r]-c[l-1]) { puts("0"); continue; }
printf("%d\n",SEG.query(rt[l-1],rt[r],k));
}
return 0;
}
F. Download Speed Monitor
簽到模擬題,驗題時輸出的要求好像時截斷兩位小數來著,但好像因為會有精度誤差啥的後面改成了保留六位小數了
程式碼還是驗題時的版本,反正大差不差
#include<cstdio>
#include<iostream>
#include<cmath>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,a[N],pfx[N];
signed main()
{
RI i; for (scanf("%lld%lld",&n,&k),i=1;i<=n;++i)
scanf("%lld",&a[i]),pfx[i]=pfx[i-1]+a[i];
for (i=k;i<=n;++i)
{
int sum=pfx[i]-pfx[i-k];
if (sum<1024LL*k)
{
int tmp=floor((long double)(sum)*100/k);
printf("%lld.",tmp/100); tmp%=100;
printf("%lld%lld KiBps\n",tmp/10,tmp%10);
} else
{
int tmp=floor((long double)(sum)*100/k/1024);
printf("%lld.",tmp/100); tmp%=100;
printf("%lld%lld MiBps\n",tmp/10,tmp%10);
}
}
return 0;
}
G. Download Time Monitor
又是簽到模擬題,但有人因為用cin
讀入double
太慢導致TLE了好幾發,我不好評價
做法本身沒啥好說的,注意要用int
讀入再轉成double
操作
#include<bits/stdc++.h>
using namespace std;
using LD = double;
#define Tp template <typename T>
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
char Fin[S],*A,*B;
public:
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
#undef tc
}F;
int tt;
signed main(){
//freopen("1.out", "r", stdin);
//freopen("1.in", "w", stdout);
cout << setiosflags(ios::fixed) << setprecision(9);
cin >> tt;
while (tt--){
int B_, t1_, a1_, t2_, a2_;
F.read(B_); F.read(t1_); F.read(a1_); F.read(t2_); F.read(a2_);
LD B=B_, t1=t1_, a1=a1_, t2=t2_, a2=a2_;
LD ans1=0.0L, ans2=0.0L;
if (1.0L*a1/B + t1 <= t2){
ans1 = 1.0L*a1/B;
ans2 = 1.0L*a2/B;
}else{
ans1 = t2 - t1;
a1 -= (t2-t1)*B;
LD val = min(2.0L*a1/B, 2.0L*a2/B);
ans1 += val;
ans2 += val;
a1 -= val*B/2;
a2 -= val*B/2;
ans1 += a1/B;
ans2 += a2/B;
}
cout << ans1 << ' ' << ans2 << '\n';
}
return 0;
}
H. Real Estate Is All Around
唉這題比賽的時候徐神一直跟我說是個Flow,但我就是想去寫DP,最後看Tutorial發現兩種都行
但比賽時以為單組資料\(O(n^3)\)的做法會T掉(實際上跑不滿就不會),所以在已經討論出了所有關鍵性質的情況下沒去寫顯而易見的\(O(n^3)\)DP,而是去寫了一個神秘的帶悔貪心,成功把自己送走
這題的幾個關鍵的觀察就是對於小紅和小藍,我們只會把它們要賣的房子交給它們,而多餘的房子全都都扔給小綠是最優的
而小綠又每次賣售價最高的房子,這就導致每個房子要麼在交給某個人後可以確定一定會售出,要麼就相當於直接扔掉了這個房子
考慮直接使用DP求解,不妨設\(f_{i,x,y,z}\)表示已經處理了前\(i\)個操作,每個人手上存有的房子數分別為\(x,y,z\)的最大收益,轉移十分trivial,但複雜度是\(O(n^4)\)的
考慮如何最佳化,顯然小紅和小藍收取的中介費和房子本身的售價沒有任何關係,因此我們可以把交給他們兩個的房子合併到一起去
區別是在遇到第二類事件時,如果選擇從他們倆那拿出兩套房子的話,就需要額外支付\(1\)的代價,否則我們總是貪心地把房子給小藍
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=205,INF=1e9;
int t,n,tp,x,f[N][N][N];
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j,k; scanf("%d",&n);
for (i=0;i<=n;++i) for (j=0;j<=n;++j) for (k=0;k<=n;++k) f[i][j][k]=-INF;
for (f[0][0][0]=i=0;i<n;++i)
{
if (scanf("%d",&tp),tp==1)
{
for (scanf("%d",&x),j=0;j<=i;++j) for (k=0;j+k<=i;++k)
if (f[i][j][k]!=-INF)
{
f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]);
f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+x-(x+9)/10);
f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+x);
}
} else
{
for (j=0;j<=i;++j) for (k=0;j+k<=i;++k)
if (f[i][j][k]!=-INF)
{
f[i+1][max(0,j-1)][max(0,k-1)]=max(f[i+1][max(0,j-1)][max(0,k-1)],f[i][j][k]);
f[i+1][max(0,j-1)][max(0,k-2)]=max(f[i+1][max(0,j-1)][max(0,k-2)],f[i][j][k]-1);
}
}
}
printf("%d\n",f[n][0][0]);
}
return 0;
}
網路流的做法也比較經典,但比賽的時候沒仔細想,賽後看了眼Tutorial的建圖感覺還是挺經典的
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=205,INF=1e9;
int T,n,tp,idx,sum,x;
namespace MinCost_MaxFlow
{
const int NN=1005;
struct edge
{
int to,nxt,v,c;
}e[1000005]; int head[NN],cur[NN],s,t,cnt=1,dis[NN]; bool vis[NN];
inline void addedge(CI x,CI y,CI v,CI c)
{
//printf("%d %d %d %d\n",x,y,v,c);
e[++cnt]=(edge){y,head[x],v,c}; head[x]=cnt;
e[++cnt]=(edge){x,head[y],0,-c}; head[y]=cnt;
}
#define to e[i].to
inline bool SPFA(CI s,CI t)
{
RI i; for (i=1;i<=idx;++i) dis[i]=INF; dis[s]=0;
queue <int> q=queue <int>(); q.push(s);
while (!q.empty())
{
int now=q.front(); q.pop(); vis[now]=0;
for (i=head[now];i;i=e[i].nxt)
if (e[i].v&&dis[now]+e[i].c<dis[to])
if (dis[to]=dis[now]+e[i].c,!vis[to]) q.push(to),vis[to]=1;
}
return dis[t]!=INF;
}
inline int DFS(CI now,CI tar,int tmp,int& flow,int& cost)
{
if (now==tar) return flow+=tmp,tmp;
int ret=0; vis[now]=1;
for (RI& i=cur[now];i&&dis;i=e[i].nxt)
if (!vis[to]&&e[i].v&&dis[to]==dis[now]+e[i].c)
{
int dist=DFS(to,tar,min(tmp-ret,e[i].v),flow,cost);
ret+=dist; e[i].v-=dist; e[i^1].v+=dist; cost+=dist*e[i].c;
if (ret==tmp) break;
}
vis[now]=0; return ret;
}
#undef to
inline void Dinic(CI s,CI t,int& flow,int& cost)
{
flow=cost=0; while (SPFA(s,t)) memcpy(cur,head,idx+1<<2),DFS(s,t,INF,flow,cost);
}
inline void clear(void)
{
for (RI i=1;i<=idx;++i) head[i]=0; cnt=1;
}
};
using namespace MinCost_MaxFlow;
int main()
{
for (scanf("%d",&T);T;--T)
{
RI i,j; scanf("%d",&n); s=1; t=2; sum=0;
auto ID=[&](CI x,CI y)
{
return (x-1)*3+y+2;
};
for (idx=ID(n,3),i=1;i<=n;++i)
{
if (i!=n)
{
for (j=1;j<=3;++j) addedge(ID(i,j),ID(i+1,j),INF,0);
}
if (scanf("%d",&tp),tp==1)
{
scanf("%d",&x); sum+=x;
++idx; addedge(s,idx,1,0);
addedge(idx,ID(i,1),1,1);
addedge(idx,ID(i,2),1,(x+9)/10);
addedge(idx,ID(i,3),1,0);
addedge(idx,t,1,x);
} else
{
for (j=1;j<=3;++j) addedge(ID(i,j),t,1,0);
}
}
int flow,cost; Dinic(s,t,flow,cost);
printf("%d\n",sum-cost); clear();
}
return 0;
}
I. Integer Reaction
很套路的題,最小值最大一眼二分答案\(lim\),考慮如何檢驗
用兩個multiset
分別儲存當前兩種顏色還未發生反應的集合,對於一個新元素\(x\):
- 若不存在與它顏色相反的元素,則將其加入它對應顏色的
multiset
中 - 若存在與它顏色相反的元素,則嘗試找到最小的\(y\)滿足\(x+y\ge lim\),即等價於查詢\(lim-x\)的後繼
總複雜度\(O(n\log n\log a_i)\)
#include<cstdio>
#include<iostream>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],c[N];
inline bool check(CI x)
{
multiset <int> s[2];
for (RI i=1;i<=n;++i)
{
if (s[c[i]^1].empty())
{
s[c[i]].insert(a[i]); continue;
}
auto it=s[c[i]^1].lower_bound(x-a[i]);
if (it==s[c[i]^1].end()) return 0;
s[c[i]^1].erase(it);
}
return 1;
}
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=1;i<=n;++i) scanf("%d",&c[i]);
int l=1,r=2e8,mid,ret; while (l<=r)
if (check(mid=l+r>>1)) ret=mid,l=mid+1; else r=mid-1;
printf("%d\n",ret);
return 0;
}
J. Tile Covering
剛開始硬上了個按行狀壓的東西,然後發現狀態數爆炸了只能跑\(15\)左右
大致思路就是狀壓每行的狀態,不難發現如果有連著的一段\(1\)(長度大於等於\(2\)),那麼這裡一定是橫著放的一塊骨牌,則下面一行這些位置都必須放\(0\)
否則對於單獨的\(1\),我們預設其是豎著放的,這樣下一行還能放\(1\),然後爆搜一下轉移狀態,然後就寄了
後面看知乎好像有高手用這種寫法過了,不過可能還要在加個高位字首和最佳化之類的,但也懶得去補了
#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=(1<<18)+5;
int n,m,a[20][20],c[20],t[20],cs[20],f[20][N],g[20][N];
vector <int> vec[N],valid[20];
inline void DFS1(vector <int>& v,CI cur,CI mask)
{
if (cur>=m) return v.push_back(mask);
if (t[cur]!=-1) return cs[cur]=t[cur],DFS1(v,cur+1,mask);
if (cur>0&&c[cur-1]==1&&cs[cur-1]==1)
return cs[cur]=0,DFS1(v,cur+1,mask);
if (cur>0&&cs[cur-1]==1&&c[cur]==1)
return cs[cur]=0,DFS1(v,cur+1,mask);
cs[cur]=0; DFS1(v,cur+1,mask);
cs[cur]=1; DFS1(v,cur+1,mask|(1<<cur));
}
inline void DFS2(CI rid,CI cur,CI mask,CI sum)
{
if (cur>=m) return (void)(valid[rid].push_back(mask),g[rid][mask]=sum);
if (a[rid][cur]<=0) return DFS2(rid,cur+1,mask,sum);
DFS2(rid,cur+1,mask,sum);
DFS2(rid,cur+1,mask|(1<<cur),sum+a[rid][cur]);
}
int main()
{
RI i,j; for (scanf("%d%d",&n,&m),i=0;i<n;++i)
for (j=0;j<m;++j) scanf("%d",&a[i][j]);
for (i=0;i<(1<<m);++i)
{
for (j=0;j<m;++j) c[j]=(i>>j)&1,t[j]=-1;
for (j=0;j<m;++j)
if (c[j]&&((j>0&&c[j-1])||(j<m-1&&c[j+1]))) t[j]=0;
DFS1(vec[i],0,0);
}
/*int all=0; for (i=0;i<(1<<m);++i)
{
all+=vec[i].size();
for (auto x:vec[i]) printf("%d %d\n",i,x);
}
printf("%d\n",all);*/
memset(g,-1,sizeof(g));
for (i=0;i<n;++i) DFS2(i,0,0,0);
memset(f,-1,sizeof(f));
for (auto mask:valid[0]) f[0][mask]=g[0][mask];
for (i=0;i<n-1;++i)
for (auto mask:valid[i]) if (~f[i][mask])
for (auto nxt:vec[mask]) if (~g[i+1][nxt])
f[i+1][nxt]=max(f[i+1][nxt],f[i][mask]+g[i+1][nxt]);
int ans=0; for (auto mask:valid[n-1]) ans=max(ans,f[n-1][mask]);
return printf("%d",ans),0;
}
後面發現了更本質的限制,其實就是放棋子的位置不能出現拐彎
那麼可以用插頭DP,設\(f_{i,j,mask}\)表示從上到下,從左到右處理到\((i,j)\)位置,此時的輪廓線狀壓狀態為\(mask\)時的最優權值和
和常規的插頭DP的區別就是要多記錄一個\((i-1,j)\)的狀態,其它的基本沒什麼區別
總複雜度\(O(nm\times 2^m)\)
#include <bits/stdc++.h>
int a[20][20];
int dp_base[2][2][1 << 18];
auto dp1 = dp_base[0], dp2 = dp_base[1];
void chkmx(int &a, const int b) {
if(b > a) a = b;
}
int main() {
memset(dp_base, 0x80, sizeof(dp_base));
// std::cout << sizeof(dp_base) / 1024.L / 1024.L << char(10);
int n, m;
std::cin >> n >> m;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) std::cin >> a[i][j];
dp1[0][0] = 0;
const int U = (1 << m) - 1;
for(int i = 1; i <= n; ++i) for(int j = 0; j < m; ++j) {
memset(dp2, 0x80, sizeof(dp_base[0]));
for(int b = 0; b < 2; ++b) for(int s = 0; s < (1 << m); ++s) {
// for dp1 at x, y
const int x = i, y = j;
const int cur = (s >> y) & 1;
const int pre = (y == 0 ? 0 : ((s >> y - 1) & 1));
const int nxt = (s >> y + 1) & 1;
const int nb = (y == m - 1 ? 0 : cur);
chkmx(dp2[nb][s & (U ^ (1 << y))], dp1[b][s]);
if(pre && b || b && cur || cur && nxt || cur && pre) continue;
chkmx(dp2[nb][s | (1 << y)], dp1[b][s] + a[x][y + 1]);
}
std::swap(dp1, dp2);
// for(int b = 0; b < 2; ++b) for(int s = 0; s < (1 << m); ++s)
// std::cerr << "dp[" << i + (j == m - 1) << "][" << (j + 1) % m << "]["
// << b << "][" << s << "] = " << dp1[b][s] << char(10);
}
int ans = 0;
for(int b = 0; b < 2; ++b) for(int s = 0; s < (1 << m); ++s)
// std::cerr << "dp[" << b << "][" << s << "] = " << dp1[b][s] << char(10),
chkmx(ans, dp1[b][s]);
std::cout << ans << char(10);
return 0;
}
/*
4 4
-1 2 1 -2
3 2 0 -2
-3 -2 0 3
2 3 0 1
*/
K. Number Deletion Game
被祁神一眼秒了的博弈題,不難發現勝負僅與最大數出現次數的奇偶性有關,證明可以採用歸納法
#include<bits/stdc++.h>
using namespace std;
int n;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
int mx=0, cnt=0;
for (int i=1; i<=n; ++i){
int a; cin >> a;
if (a>mx) mx=a, cnt=1;
else if (a==mx) ++cnt;
}
cout << (cnt%2==1 ? "Alice\n" : "Bob\n");
return 0;
}
Postscript
又被隊友帶飛了,最近打的真有點混吧……