2023 USACO Dec G 组

\(1:10\) AK。

T1

注意到路线是从小的到大的点,因此可以从后往前确定。具体地说,确定一个点 \(i\),从前往后枚举 \(j>i\),如果现在到 \(j\) 的路线个数不符合奇偶性,就连一条边。

很容易证明是正确的。\(\mathcal{O}(N^3)\)

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 755;

int n;
char c[N][N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n;
	for (int i=1; i<n; i++){
		for (int j=i+1; j<=n; j++){
			cin>>c[i][j];
		}
	}
	int ans=0;
	for (int i=n-1; i>=1; i--){
		vector<int> cnt(n+1,0);
		for (int j=i+1; j<=n; j++){
			if ((cnt[j]+(c[i][j]-'0'))&1){
				ans++;
				for (int k=j+1; k<=n; k++){
					cnt[k]+=c[j][k]-'0';
				}
			}
		}
	}
	cout<<ans<<endl; 
	return 0;
}

T2

首先,最长的路径长度非常好求(直接 topo sort)。要确保字典序最小,就要确定

  • 第一个边的权值是可能中的最小的。

  • 满足第一个条件后,所有路径当中去掉第一个边,字典序最小的。

发现解决最长路径为 \(d\) 的问题,转化成了如果能求出为 \(d-1\) 的所有点的最小字典序的 rank,就可以了。其次发现,topo sort 的时候一定是 \(d-1\)\(d\) 前面。

因此就做完了。具体看代码。\(\mathcal{O}(N\log N)\)

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 4e5+5;
const int inf = 2e9;

int n,m,dis[N],in[N],rnk[N];
ll sum[N];
vector<pair<int,int> > g[N],rg[N]; 
pair<ll,ll> ans[N];

struct node {
	int vl,rk,id;
	bool operator < (const node &b) const {
		return vl!=b.vl?vl<b.vl:rk<b.rk;
	}
};

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m;
	for (int i=1; i<=m; i++){
		int a,b,l;
		cin>>a>>b>>l;
		in[a]++;
		rg[a].push_back({b,l});
		g[b].push_back({a,l});
	}
	memset(dis,-1,sizeof dis);
	int cur=0;
	queue<int> q;
	vector<node> v;
	for (int i=1; i<=n; i++){
		if (!in[i]){
			dis[i]=0;
			q.push(i);
		}
	}
	while (!q.empty()){
		int x=q.front();
		q.pop();
		if (cur!=dis[x]){
			sort(v.begin(),v.end());
			for (int i=0; i<v.size(); i++){
				rnk[v[i].id]=i;
			}
			v.clear();
			cur=dis[x];
		}
		node it={inf,inf,x};
		ll mx=0,psum=0;
		for (auto p : rg[x]){
			int y=p.first,l=p.second;
			node itt={l,rnk[y],x};
			if (itt<it&&mx==dis[y]+1 || mx<dis[y]+1){
				psum=sum[y]+l;
				mx=dis[y]+1;
				it=itt;
			}
		}
		ans[x]={mx,psum};
		sum[x]=psum;
		v.push_back(it);
		for (auto p : g[x]){
			int y=p.first,l=p.second;
			in[y]--;
			if (!in[y]){
				dis[y]=dis[x]+1;
				q.push(y);
			}
		}
	}
	for (int i=1; i<=n; i++){
		cout<<ans[i].first<<" "<<ans[i].second<<"\n";
	}
	return 0;
}

听说有 hash 的做法,但是不会。

T3

先给出一个离线的 \(\mathcal{O}(N\log N)\) 的做法。

离线下来,按照 \(\frac{a}{b}\) 从大到小排序。很容易得知,出发的位置是按照排序越来越大的。

根据人类智慧,是单谷函数,因此可以直接维护一个东西从左往右扫。

Code
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 1e6+6;

struct qy {
	ll a,b;
	int id;
	bool operator < (const qy &x) const {
		// a/b>x.a/x.b
		return a*x.b>x.a*b;
	}
} qs[N];

ll n,q,x[N],vis[N];
ll lf[N],ri[N],ans[N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n;
	for (int i=1; i<=n; i++){
		cin>>x[i];
		vis[x[i]]++;
	}
	ll cnt=0;
	for (int i=0; i<N; i++){
		lf[i]=(i==0?0:lf[i-1])+cnt;
		cnt+=vis[i];
	}
	cnt=0;
	for (int i=N-2; i>=0; i--){
		ri[i]=ri[i+1]+cnt;
		cnt+=vis[i];
	}
	cin>>q;
	for (int i=1; i<=q; i++){
		cin>>qs[i].a>>qs[i].b;
		qs[i].id=i;
	}
	sort(qs+1,qs+1+q);
	ll cur=0;
	for (int i=1; i<=q; i++){
		ll a=qs[i].a,b=qs[i].b;
		#define cal(x) (a*lf[x]+b*ri[x])
		while (cal(cur)>cal(cur+1)){
			cur++;
		}
		ans[qs[i].id]=cal(cur);
	}
	for (int i=1; i<=q; i++){
		cout<<ans[i]<<"\n";
	}
	return 0;
}

因为单谷,所以在线做法就是三分。

听说有都少掉一个 \(\log\) 的,很想学。

posted @ 2024-01-31 11:15  SFlyer  阅读(73)  评论(0编辑  收藏  举报