HPU第二次个人训练
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;
}