오늘도 아침 9시에 일어나 있었기에 바로 시작했다.
이번에는 문제 백업을 해 놓았다. 다만 HTML만 받아 놓은 상태라서 이걸 내 형태에 맞추기 위해서는 꽤 작업이 필요해 보인다. 순차적으로 정규표현식을 적용하는 툴이 있으면 좋겠는데 어떻게 찾아야 할지 모르겠다. 어찌 됐건 아무래도 MathJax를 곧 지원하게 될 것 같다.
쉽다. N개를 앞에서부터 보면서 그때까지의 최댓값보다 큰지 아닌지를 기록하고, 뒤에서부터 보면서 그때까지의 최솟값보다 작은지 아닌지를 기록한다. 둘 모두를 만족하는 것의 개수를 세면 된다.
#include<iostream>
using namespace std;
#include<stdio.h>
int t,n,a[200010],chk1[200010],chk2[200010],out;
int main()
{
int i,j,mn,mx;
scanf("%d",&t);
for(i=1;i<=t;++i)
{
scanf("%d",&n);
for(j=1;j<=n;++j)
{
scanf("%d",&a[j]);
chk1[j]=0;
chk2[j]=0;
}
chk1[1]=1;
mx=a[1];
for(j=2;j<=n;++j)
{
if(a[j]>mx)
{
mx=a[j];
chk1[j]=1;
}
}
chk2[n]=1;
mn=a[n];
for(j=n-1;j>=1;--j)
{
if(a[j]<mn)
{
mn=a[j];
chk2[j]=1;
}
}
out=0;
for(j=1;j<=n;++j)if(chk1[j]&&chk2[j])out++;
printf("Case #%d\n%d\n",i,out);
}
return 0;
}
어디서 본 것 같은데…. 하고 일단 넘어갔다.
데이크스트라 알고리즘을 약간 변형하면 단조 증가 하는 경로 중 최단 거리를 찾을 수 있다.# 간선들을 가중치 내림차순으로 정렬한 후, 계속 더 작은 간선을 선택하도록 반복하면 된다. 이때 우선순위 큐에 최대 E번 들어갈 수 있으므로 시간복잡도는 O(ElogE) 이다.
위 과정을 거친 후 1번 정점으로부터의 값과 N번 정점으로부터의 값을 합하여 그 중 최솟값을 찾아주면 된다. 여기서 간선이 최대 100000개, 가중치가 최대 200000이므로 수의 범위에 유의해야 한다.
-1 처리와 순증가 실수로 인해 몇 번 틀렸다.
#include<iostream>
using namespace std;
#include<algorithm>
#include<queue>
#include<stdio.h>
struct Edge
{
int s,e,c;
bool operator()(Edge a,Edge b)
{
return a.c>b.c;
}
}e[200010];
int t,n,m,pos[100010];
long long d1[100010],d2[100010],out;
vector<int> idx[100010];
priority_queue<pair<long long,int> > q;
inline long long mn2(long long a,long long b)
{
if(a>b)return b;
return a;
}
void dijkstra(int s,long long d[])
{
int i;
pair<long long,int> p;
for(i=1;i<=n;++i)
{
pos[i]=0;
d[i]=0x3FFFFFFFFFFFFFFFll;
}
d[s]=0;
for(i=0;i<idx[s].size();++i)
{
q.push(make_pair(-e[idx[s][i]].c,idx[s][i]));
d[e[idx[s][i]].e]=e[idx[s][i]].c;
}
while(!q.empty())
{
p=q.top();
q.pop();
while(pos[e[p.second].e]<idx[e[p.second].e].size()&&e[idx[e[p.second].e][pos[e[p.second].e]]].c>=e[p.second].c)
{
q.push(make_pair(p.first-e[idx[e[p.second].e][pos[e[p.second].e]]].c,idx[e[p.second].e][pos[e[p.second].e]]));
d[e[idx[e[p.second].e][pos[e[p.second].e]]].e]=mn2(d[e[idx[e[p.second].e][pos[e[p.second].e]]].e],-p.first+e[idx[e[p.second].e][pos[e[p.second].e]]].c);
pos[e[p.second].e]++;
}
}
}
int main()
{
int i,j;
scanf("%d",&t);
for(i=1;i<=t;++i)
{
scanf("%d%d",&n,&m);
for(j=1;j<=m;++j)
{
scanf("%d%d%d",&e[j*2-1].s,&e[j*2-1].e,&e[j*2-1].c);
e[j*2].s=e[j*2-1].e;
e[j*2].e=e[j*2-1].s;
e[j*2].c=e[j*2-1].c;
}
sort(&e[1],&e[m*2+1],Edge());
for(j=1;j<=n;++j)idx[j].clear();
for(j=1;j<=m*2;++j)idx[e[j].s].push_back(j);
dijkstra(1,d1);
dijkstra(n,d2);
out=0x7FFFFFFFFFFFFFFFll;
for(j=1;j<=n;++j)
{
if(d1[j]==0x3FFFFFFFFFFFFFFFll||d2[j]==0x3FFFFFFFFFFFFFFFll)continue;
if(d1[j]+d2[j]<out)out=d1[j]+d2[j];
}
if(out==0x7FFFFFFFFFFFFFFFll)out=-1;
printf("Case #%d\n%lld\n",i,out);
}
return 0;
}
2017 ACM-ICPC 대전 리저널 인터넷 예선 기출 문제다.# 기출 문제다 보니 공개된 풀이도 있다.#
예전에 풀어 놓은 것이 있지 않을까 해서 컴퓨터를 열심히 뒤져 봤지만 별 소득이 없었다. 일단 결정적으로 우리 팀(AeRaMoLeuGetDaPPAP)은 그 문제를 풀지 못했었다.# 대충 보니 그리디로 열심히 해 보려다가 실패한 것처럼 보였다. 그래서 처음부터 다시 생각해 보기로 했다. N이 10000이라는 점을 이용해 O(N2) 솔루션을 찾는 것에 주력했다.
d[i][j]를 y좌표 기준으로 아래서부터 i번째까지 메모지를 배치하고 연결했을 때 j개가 꺾인 표시선일 경우 메모지가 위치한 가장 위 y좌표 중 최소값으로 정의하자. 그러면 d[1][0]은 y[1]이고 d[1][1]은 -∞이다. 비슷하게 d[i][i]는 모두 -∞이다. 그 다음으로 d[i][j]를 채우기 위한 점화 관계를 살펴보자. 먼저 i번째 점에 대해 메모지와 연결할 때 연결선이 꺾일 수 있는 것으로 추가하면 d[i-1][j-1]+h[i]의 값이 가능하다. 물론 j가 1 이상이어야 한다. 수평선으로만 연결하고자 한다면, 즉 d[i-1][j]에서 가져오려면 d[i-1][j]가 y[i] 이하임을 확인해야 한다. 만약 그렇지 않다면 수평선으로만은 연결할 수 없기 때문이다. 이 조건을 만족한다면 max(d[i-1][j]+h[i],y[i])의 값도 가능하다. "가능하다"고 표현한 것은 불가능한 경우도 있기 때문이다. 조건이 모두 안 맞는다면 불가능하다고 표시해 준다. 최종적으로 d[n][i] 중 가능한 값이 들어 있는 가장 작은 i를 찾아주면 최소 꺾은 표시선의 개수를 알 수 있다.
N이 10000이므로 이를 10000×10000 배열을 잡아서 구현하기는 곤란하다. d[i][j]를 구하기 위해서는 d[i-1][j-1]과 d[i-1][j] 만을 사용한다는 것을 활용하면 2행만으로 구성할 수 있다.
#include<iostream>
using namespace std;
#include<algorithm>
#include<stdio.h>
struct Point
{
int y,h;
bool operator()(Point a,Point b)
{
return a.y<b.y;
}
}a[10010];
int t,n,d[2][10010];
inline int mn2(int a,int b)
{
if(a>b)return b;
return a;
}
inline int mx2(int a,int b)
{
if(a>b)return a;
return b;
}
int main()
{
int i,j,k,x;
scanf("%d",&t);
for(i=1;i<=t;++i)
{
scanf("%d",&n);
for(j=1;j<=n;++j)scanf("%d%d%d",&x,&a[j].y,&a[j].h);
sort(&a[1],&a[n+1],Point());
d[1][0]=a[1].y;
d[1][1]=-1999999999;
for(j=2;j<=n;++j)
{
for(k=0;k<j;++k)
{
d[j%2][k]=1999999999;
if(k)d[j%2][k]=d[(j-1)%2][k-1]+a[j].h;
if(d[(j-1)%2][k]<=a[j].y)d[j%2][k]=mn2(d[j%2][k],mx2(d[(j-1)%2][k]+a[j].h,a[j].y));
}
d[j%2][j]=-1999999999;
}
for(j=0;j<=n;++j)if(d[n%2][j]<1999999999)break;
printf("Case #%d\n%d\n",i,j);
}
return 0;
}
먼저 M개의 수와 패턴이 있을 때 일치하는지 검사를 어떻게 할지 생각해 보았다. map을 이용하면 O(MlogM) 만에 할 수 있다. M개의 수를 키로 하고 값은 위치로 하여 map에 넣은 후, iterator를 통해 순회하면서 그 위치의 패턴 값이 단조 증가하는지를 파악하면 된다.
그렇다면 이제 최대 K개의 수와 해당하는 패턴을 빼는 것에 대해서 고려하는 것은 LIS라는 간단한 문제로 단순화되는 것을 관찰할 수 있다. 이 역시 O(MlogM) 이다. 따라서 전체 시간복잡도는 O(NMlogM) 이다.
코드는 매우 간단하다.
#include<iostream>
using namespace std;
#include<algorithm>
#include<map>
#include<stdio.h>
int t,n,m,k,a[10010],b[310],c[310],len,out;
map<int,int> p;
int main()
{
int i,j,s,pos;
map<int,int>::iterator it;
scanf("%d",&t);
for(i=1;i<=t;++i)
{
scanf("%d%d%d",&n,&m,&k);
for(j=1;j<=n;++j)scanf("%d",&a[j]);
for(j=1;j<=m;++j)scanf("%d",&b[j]);
out=0;
p.clear();
for(j=1;j<m;++j)p[a[j]]=j;
for(;j<=n;++j)
{
p[a[j]]=j;
len=0;
for(it=p.begin();it!=p.end();++it)
{
pos=lower_bound(&c[0],&c[len],b[it->second-j+m])-c;
if(pos>=len)len++;
c[pos]=b[it->second-j+m];
}
if(len>=m-k)out++;
p.erase(a[j-m+1]);
}
printf("Case #%d\n%d\n",i,out);
}
return 0;
}
해결하지 못했다.
6시간 정도에 마무리했다. 아래는 그 당시 상황이다.
두 번 연속 생각보다 좋은 결과를 냈다. 이 정도면 거의 본선 진출일 것 같은데 본선에서도 좋은 결과가 있었으면 좋겠다.
최종 공식적으로 발표된 결과는 아래와 같다. 예상대로 본선에 진출했다. 들리는 얘기로는 400점대까지도 본선에 진출했다고 한다. 3번 문제가 꽤 시간제한이 빡빡했다고 하는데 나는 못 느끼기는 했다.