HPU第二次個人訓練
A.Engines
題目鏈接:https://abc139.contest.atcoder.jp/tasks/abc139_f?lang=en
題目大意:給你n個位置,依次走到這些點,問在此過程中距離原點最遠時的距離
解題思路:把每個點看成一個向量,兩向量夾角越小,合成後的距離越大,因此我們可以用極角排序,然後暴力求出最遠距離
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e2+5;
struct Point{
int x,y;
}p[MAXN];
bool cmp(Point a,Point b) {
return atan2(a.y,a.x) < atan2(b.y, b.x);
}
int nx[MAXN];//讓它循環
int main() {
int n;
cin>>n;
for(int i=1;i<=n;++i) cin>>p[i].x>>p[i].y;
for(int i=1;i<=n;++i) nx[i] = i+1; nx[n] = 1;
sort(p+1,p+n+1,cmp);
ll ans=0;
for(int i=1;i<=n;++i) {
ll x = p[i].x, y = p[i].y;
cout<<p[i].x<<" "<<p[i].y<<endl;
ans = max(ans, x * x + y * y);
for(int j=nx[i];j!=i;j = nx[j]) {
x += p[j].x;
y += p[j].y;
ans = max(ans, x * x + y * y);
}
}
printf("%.10lf\n",sqrt(ans));
return 0;
}
B - Consecutive Integers
題目大意:給你兩個數n,m,問在n中可以找到多少個長度爲m的連續序列。
解題思路:n中小於等於n-m+1的點都可以作爲序列的起點,因此答案即爲n-m+1。
Code:
#include<iostream>
using namespace std;
const int N = 1e5+5;
int n,k;
int main(){
cin>>n>>k;
cout<<n-k+1<<endl;
return 0;
}
C - ModSum
題目大意:給你一個n,在n中每個數,然後再從n中選一個數作爲除數,不能重複,然後求出這些它們餘數的和最大值
解題思路:對於每個數m,當除數爲m+1時餘數最大,然後求和輸出即可。其實就是1到n-1求和。
Code:
#include<iostream>
using namespace std;
typedef long long ll;
int main(){
ll n,sum=0;
cin>>n;
sum=(n-1)*n/2;
cout<<sum<<endl;
return 0;
}
D - Shortest Path on a Line
題目鏈接:
https://nikkei2019-2-qual.contest.atcoder.jp/tasks/nikkei2019_2_qual_d?lang=en
題目大意:n,m分別代表點數和邊數,以下m行有u,v,w三個數,表示[u,v]上任意兩點之間的距離爲w。求1到n的最短路徑。
解題思路:很明顯的dijkstra(),但對於[u,v]上任意兩點之間的距離爲w該怎麼解決呢,其實我們只要把m點到m-1點的距離初始化爲0,那麼如果一個點到m點的距離爲k,我們也可以將該點到m-1點的距離也爲k。是不是非常妙!!!!詳見代碼
Code:
#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 1e6+5,M=N*2;
ll h[N],st[N],dist[N],cnt,n,m;
const ll inf = 0x3f3f3f3f3f3f3f3f;
struct node {
ll to,nxt,w;
}eg[M*2];
void add(ll u,ll v,ll w){
eg[cnt].w=w;eg[cnt].to=v;eg[cnt].nxt=h[u];h[u]=cnt++;
}
priority_queue<ll,vector<ll>,greater<ll> >q;//優先隊列
ll dijkstra(ll u){
memset(st,0,sizeof(st));
// memset(dist,0x3f,sizeof(dist));
for(ll i=1;i<=n;i++) dist[i]=inf;
dist[u]=0;
q.push(u);
while(!q.empty()){
ll u=q.top();
q.pop();
// 因爲有多次更新,所以不能添加標記
// if(st[u]) continue;//
// st[u]=1;
for(ll i=h[u];i!=-1;i=eg[i].nxt){//鏈式前向星遍歷圖
ll v = eg[i].to;
// if(st[v]) continue;
if(dist[v]>dist[u]+eg[i].w){
dist[v]=dist[u]+eg[i].w;
q.push(v);
}
}
}
if(dist[n]==inf) return -1;
return dist[n];
}
int main(){
memset(h,-1,sizeof(h));
cnt=0;
cin>>n>>m;
for(ll i=1;i<=n;i++) add(i,i-1,0);
for(ll i=0;i<m;i++){
ll u,v,w;
cin>>u>>v>>w;
add(u,v,w);
}
cout<<dijkstra(1)<<endl;
return 0;
}
E - Counting of Trees
題目鏈接:https://nikkei2019-2-qual.contest.atcoder.jp/tasks/nikkei2019_2_qual_b?lang=en
題目大意:給你一棵樹,問它的同分異構體的數目
解題思路:對於距離大於1的點,每個點放的種類數都取決於距離小於一點的個數,假設距離爲1的點的個數爲x,距離爲2的點的個數爲y,距離爲2的每個點都有x种放法。所以放法有x^y种放法。手動模擬一下即可。然後考慮不成樹的情況,列舉出來即可。
Code:
#include<iostream>
#include<map>
#include<cmath>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
const ll mod = 998244353;
ll a[N];
map<ll,ll>ma;
ll ksm(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main(){
ll n;
cin>>n;
bool flag=0;
for(ll i=1;i<=n;i++){
cin>>a[i];
ma[a[i]]++;
if(i==1){
if(a[i]!=0) flag=1;
}
else {
if(a[i]>n-1) flag=1;
}
}
if(ma[0]>1) flag=1;
for(ll i=1;i<=n-1;i++){
if(ma[i]>0&&ma[i-1]==0) flag=1;
// if(ma[i]==0) break;
}
ll ans=1;
if(flag) cout<<0<<endl;
else {
for(ll i=1;i<=n-1;i++){
if(ma[i]&&ma[i-1]) ans=ans*ksm(ma[i-1],ma[i])%mod;
else break;
}
cout<<ans%mod<<endl;
}
return 0;
}
F - Monsters Battle Royale
題目大意:這道題就是找最後可以剩下的最小的生命值,由於攻擊者在攻擊時並不減少血量,所以所剩下最小的一定小於等於初始生命值最小的怪獸,當一個怪獸生命值是另一個怪獸生命值的倍數的時候,那麼另一個怪獸就可以完全被生命值小的那個怪獸消滅掉。
解題思路:那麼就可以從小到大排序,每一次都貪心地把最小的數作爲攻擊者,去攻擊其他的數字(也就是大的取餘小的),然後再一次排序,循環這個過程,直到出現1或者數字只剩一個非0數。
Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
ll n,a[N];
int main(){
cin>>n;
for(ll i=0;i<n;i++) cin>>a[i];
ll t=0,num=0;
while(1){
sort(a+t,a+n);
t=t+num;
if(t==n-1) break;
num=0;
for(int i=t+1;i<n;i++){
a[i]%=a[t];
if(a[i]==0) num++;
}
}
cout<<a[n-1]<<endl;
return 0;
}
G - Powerful Discount Tickets
題目大意:給你一些優惠券,然後求用優惠券買東西所花最少的錢是多少。
解題思路:優先隊列將所有東西價錢從大到小排序,讓最大的使用優惠券,因此可以求得最小价錢。
Code:
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 1e5+5;
ll n,m;
int main(){
priority_queue<ll>q;
cin>>n>>m;
for(ll i=0;i<n;i++){
ll x;
cin>>x;
q.push(x);
}
for(ll i=0;i<m;i++){
ll num=q.top();
q.pop();
num=num/2;
q.push(num);
}
ll ans=0;
while(q.size()){
ans+=q.top();
q.pop();
}
cout<<ans<<endl;
return 0;
}
H - Attack Survival
題目大意:玩m輪遊戲,n個人的生命值爲k,問m輪後如果該人活着輸出“Yes”,否則輸出“No”.
解題思路:將每個人生命值置爲0,若該輪遊戲誰贏了,誰的生命值就加一,n輪後讓每個人生命值加上k-m,若生命值小於等於0則輸出’‘No’’,反之輸出“Yes”.
Code:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
ll a[N];
int main(){
ll n,m,k;
cin>>n>>m>>k;
// memset(a,0,sizeof(a));
for(int i=0;i<k;i++){
int x;
cin>>x;
a[x]+=1;
}
for(int i=1;i<=n;i++){
a[i]+=m-k;
if(a[i]<=0) puts("No");
else puts("Yes");
}
return 0;
}
I - Lower
題目大意:有n個柱子,移動規則爲向右且柱子不能高於現在所處柱子的高度,爲最遠移動距離
解題思路:求最長不上升序列
Code:
#include<iostream>
using namespace std;
typedef long long ll;
const int N =1e5+5;
ll a[N];
int main(){
int n;
cin>>n;
a[0]=-1;
ll ans=0,temp=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]<=a[i-1]) temp++;
else temp=0;
ans=max(ans,temp);
}
cout<<ans<<endl;
return 0;
}
J - Kleene Inversion
題目鏈接:https://jsc2019-qual.contest.atcoder.jp/tasks/jsc2019_qual_b?lang=en
題目大意:給你一段長度爲n的序列然後序列重複k次,然後求出改變後序列每個值後面小於它的數,然後求它們的和
解題思路:將初始序列遍歷一遍,對於每個點求出該序列中所有小於它的數的個數和該點之後小於它的點的個數。對於每一段序列其實都是初始序列的重複。詳見代碼。
Code:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const ll N = 5005;
const ll mod = 1e9+7;
ll n,k;
ll f[N],h[N],a[N],sum[N];
int main(){
// memset(sum,0,sizeof(sum));
// memset(f,0,sizeof(f));
// memset(h,0,sizeof(h));
// memset(a,0,sizeof(a));
cin>>n>>k;
for(ll i=1;i<=n;i++) cin>>a[i];
for(ll i=1;i<=n;i++){
for(ll j=i+1;j<=n;j++){
if(a[i]>a[j]) h[i]++;
}
for(ll j=1;j<i;j++){
if(a[i]>a[j]) f[i]++;
}
sum[i]=f[i]+h[i];
}
ll ans=0;
for(ll i=1;i<=n;i++){
ans=((ans+(sum[i]*((k-1)*k/2%mod)%mod)%mod)%mod+h[i]*k)%mod;//對可能爆long long的情況進行處理
}
cout<<ans%mod<<endl;
return 0;
}
K - Two Contests
題目鏈接:https://agc040.contest.atcoder.jp/tasks/agc040_b?lang=en
題目大意:將所有區間分爲兩個集合,問兩個集合所形成的區間相加的最大值是多少。
解題思路:考慮左端點最大的區間 和右端點最小的區間
如果 屬於同一個集合(設爲 ,另一個集合設爲 ),那麼其他的區間不管是不是在 都不會影響 的交集大小
那麼爲了最優顯然我們只要留一個最長的區間給 ,然後其他全給
代碼實現的時候枚舉不屬於 的最長區間時也可以考慮 的區間長度,並不影響答案
然後考慮 不屬於同一個集合的情況,不妨設 在 , 在
設第 個區間的左端點爲 ,右端點爲
那麼答案爲
現在問題就是求這個式子的最大值
這樣即可保證枚舉到式子 中的 的所有情況
具體維護的話就預處理前綴後綴 即可
代碼實現的時候同樣可以不用強制 不屬於同一個集合,因爲不影響答案
Code:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
int n;
ll lx[N],rx[N],ly[N],ry[N];
struct node {
ll l,r;
}a[N];
bool cmp(node a,node b){
return a.l<b.l;
}
int main(){
cin>>n;
ll maxx=0,minn=1e18,ans1,maxlen=0;
for(int i=1;i<=n;i++){
cin>>a[i].l>>a[i].r;
maxx=max(a[i].l,maxx);
minn=min(a[i].r,minn);
maxlen=max(a[i].r-a[i].l+1,maxlen);
}
ans1=maxlen+max(minn-maxx+1,0ll);
sort(a+1,a+n+1,cmp);
lx[1]=a[1].l;rx[1]=a[1].r;
ll rq=a[1].r;
for(int i=2;i<=n;i++){
lx[i]=max(lx[i-1],a[i].l);
rx[i]=min(rx[i-1],a[i].r);
rq=min(rq,a[i].r);
}
ry[n]=a[n].r;ly[n]=a[n].l;
ll lp=a[n].l;
for(int i=n-1;i>=1;i--){
ry[i]=min(a[i].r,ry[i+1]);
ly[i]=max(a[i].l,ly[i+1]);
lp=max(lp,a[i].l);
}
// cout<<ans1<<endl;
ll ans2=0;
// for(int i=1;i<=n-1;i++){
// ans2=max(ans2,max(rx[i]-lx[i]+1,0ll)+max(ry[i+1]-ly[i+1]+1,0ll));
// }
for(int i=1;i<=n-1;i++){
ans2=max(ans2,max(rq-lx[i]+1,0ll)+max(ry[i+1]-lp+1,0ll));
}
// cout<<ans2<<endl;
cout<<max(ans1,ans2)<<endl;
return 0;
}
L - Get Everything
題目鏈接:https://abc142.contest.atcoder.jp/tasks/abc142_e?lang=en
題目大意:有編號1到n的鎖,然後有m種鑰匙,每種鑰匙可以開一種或多種鎖,且價錢不同,問打開所有鎖所需要的最少價錢是多少
解題思路:狀壓dp,但我看了題解之後還是不太懂,先把題解貼出來吧
Code:
#include<bits/stdc++.h>
using namespace std;
int n, m, a, b, c, dp[4096];
int main() {
cin >> n >> m;
int s = (1 << n);
dp[0] = 0;
for(int i = 1; i < s; i++) dp[i] = 1e9;
while(m--) {
cin >> a >> b;
int t = 0;
while(b--) {
cin >> c;
t |= (1 << --c);
}
for(int i = 0; i < s; i++) {
if(dp[i] != 1e9) dp[i | t] = min(dp[i | t], dp[i] + a);
}
}
cout << (dp[s-1] == 1e9 ? -1 : dp[s-1]);
}
M - AB Substrings
題目鏈接:https://diverta2019.contest.atcoder.jp/tasks/diverta2019_c?lang=en
題目大意:給你一些字符串問將這些字符串怎麼組合所得的AB序列最多,當時寫的時候wa了好多次,對思維的嚴密性有一定的鍛鍊
解題思路:統計最後一個字母爲A開頭字母爲B的個數,對兩種情況都包括的進行特殊處理即可
Code:
#include<iostream>
using namespace std;
int num1,num2,num3,n,ans;
string s;
int main(){
cin>>n;
num1=num2=num3=ans=0;
for(int i=0;i<n;i++){
cin>>s;
int len = s.length();
if(s[0]=='B'&&s[len-1]=='A') num3++;
else if(s[0]=='B') num2++;
else if(s[len-1]=='A') num1++;
for(int j=0;j<len;j++){
if(j&&s[j]=='B'&&s[j-1]=='A') ans++;
}
}
// cout<<num1<<" "<<num2<<" "<<num3<<endl;
int temp=min(num1,num2);
if(num1==0&&num2==0) ans=ans+max(num3-1,0);
else if(num1==0) ans+=num3;
else if(num2==0) ans+=num3;
else ans=(ans+temp+num3);
cout<<ans<<endl;
return 0;
}