Problem:
最長増加列問題

時は過ぎて太郎君は高校生になりました。
大学生だったお兄さんの影響も受け、コンピューターサイエンスに興味を持ち始めました。

太郎君はコンピューターサイエンスの教科書を読み進め、「最長増加部分列問題」という有名問題があることを知りました。太郎君はこの問題のことを理解しましたが、自分でも類似問題が作れないものかと気になりました。

そこで太郎君は試行錯誤の結果に次のような問題を作りました。

n個の整数で構成される数列Aがある

m-1個の区切りを入れて、数列Aをm個の数列に分解する。なお、分解後のそれぞれの数列は1つ以上の数を必ず含まなければならない。

このm個それぞれの数列内の整数をすべて足し合わせた結果出来上がるm個の数を、元の数列の順に配置すると厳密な増加列になっている(つまり、出来上がる数列はBi < Bi+1をみたす)ようにしたい。

目標は最終的にできる数列Bの長さmを最大化することである。

例えば、数列Aが{5,-4,10,-3,8}の場合を考えてみる。

区切りの位置を表す数列Cを用意し、C={2,4}とする。

このとき、数列Aは(5,-4),(10,-3),(8)の部分に分かれ、これらの内部を足し合わせるとそれぞれ1,7,8となり、出来上がる数列Bは{1,7,8}となる。

太郎君はこの問題について"最長増加列問題"と名付け、これを解くアルゴリズムも考えました。

そして社会人になったあなたにチェックをしてほしいと連絡をしました。

あなたの仕事は、成長した太郎君の作った問題を解くプログラムを作ることです。

チェックするのが仕事なので、m-1個の区切りの位置も出力します。
なお、最適なmに対してこのような区切り方が複数考えられる場合もありますが、このmが正しく出力されていれば、考えられるもののうち一つを出力すればよいです。

Input

改行区切りでn+1個の整数が与えられる。

n
A1
A2
...
An

nは与えられる数列Aの長さを表す

Aiは数列Aのi番目の要素を表す。

Constraints

1≤n≤4000
|Ai|≤ 108　

整数kに対して|k|はkの絶対値を表す

Output

m
C1 C2 .. Cm-1

1行目は一つの整数mを出力する。
mは最終的にできる数列Bの長さを表す。
2行目はm-1個の整数Ciを空白区切りで出力する。
Ciは、数列Aをm個の部分に適切に区切った時のi番目の区切り場所を表す。
区切り場所の定義は図を参照せよ。なお、1≤Ci<n を満たす。
2行目のm-1個の整数列Cは昇順に並んでいなければならないしたがって、i < j ならば
Ci < Cj が成立する。

m=1の場合2行目は空行になる。

数列Cに従って数列Aから数列Bを生成したとき、数列Bが増加列になっていないとき(すなわちBi≥Bi+1となるi(1≤i<m)が存在するとき)、WrongAnswerとなる

Sample Input 1

3
1
2
4

Output for the Sample Input 1

3
1 2

もともと増加列なので、すべての場所に区切りを入れればよい

Sample Input 2

3
2
2
2

Output for the Sample Input 2

2
1

2 2 2は増加列でないので、3つに分割することはできない

Sample Input 3

3
4
2
1

Output for the Sample Input 3

1
(空行)

どう分割しても増加列にはならないため、分割をしない

Solution in JAVA (judge verdict: Runtime Error)

import java.util.*;
import java.util.Map.Entry;
import java.lang.*;
import java.math.*;
import java.io.*;
import static java.lang.Math.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;

public class Main{
	Scanner sc=new Scanner(System.in);

	int INF=1<<28;
	double EPS=1e-12;

	int n;
	long[] a;

	void run(){
		n=sc.nextInt();
		a=new long[n+1];
		for(int i=0; i<n; i++){
			a[i+1]=sc.nextInt();
		}
		a[0]=Long.MIN_VALUE;
		solve();
	}

	@SuppressWarnings("unchecked")
	void solve(){
		TreeMap<Long, Integer>[] dp=new TreeMap[n+1];
		HashMap<Long, Integer>[] cuts=new HashMap[n+1];
		for(int i=0; i<=n; i++){
			dp[i]=new TreeMap<Long, Integer>();
			cuts[i]=new HashMap<Long, Integer>();
		}
		dp[0]=new TreeMap<Long, Integer>();
		dp[0].put(Long.MIN_VALUE, 0);

		for(int j=1; j<=n; j++){
			if(j%100==0){
				debug(j, dp[j-1].size());
				// debug(Runtime.getRuntime().totalMemory()/1e6);
			}
			long sum=a[j];
			// dp[j]=new TreeMap<Long, Integer>();
			// cuts[j]=new TreeMap<Long, Integer>();
			for(int i=j-1; i>=0; i--){
				// (i, j]をマージ
				// sum未満で最大のをサーチ
				Entry<Long, Integer> entry=dp[i].lowerEntry(sum);
				if(entry!=null){
					// debug(j,sum);
//					int value=dp[j].containsKey(sum)?dp[j].get(sum):-1;
					int value=cuts[j].containsKey(sum)?dp[j].get(sum):-1;
					if(entry.getValue()+1>value){
						dp[j].put(sum, entry.getValue()+1);
						cuts[j].put(sum, i);
					}
				}
				sum+=a[i];
			}
			// debug(j, dp[j]);
			// dp[j]の調整
			int max=-1, cut=-1;
			ArrayList<Long> remove=new ArrayList<Long>();
			for(Entry<Long, Integer> entry : dp[j].entrySet()){
				long key=entry.getKey();
				int value=entry.getValue();
				/**
				 * if(entry.getValue()>max){
				 * // そのまま
				 * max=entry.getValue();
				 * cut=cuts[j].get(entry.getKey());
				 * temp.put(entry.getKey(), entry.getValue());
				 * tempCut.put(entry.getKey(), cut);
				 * }else{
				 */
				if(value>max){
					// そのまま
					max=value;
					cut=cuts[j].get(key);
					// dpTemp.put(key, value);
					// cutsTemp.put(key, cut);
				}else{
					// いらん
					// dp[j].put(entry.getKey(), max);
					// cuts[j].put(entry.getKey(), cut);
					remove.add(key);
				}
			}
			for(long key : remove){
				dp[j].remove(key);
				cuts[j].remove(key);
			}
			if(false){
				debug(j, dp[j]);
				debug(j, "cut", cuts[j]);
			}
		}

		LinkedList<Integer> list=new LinkedList<Integer>();
		long sum=Long.MAX_VALUE;
		for(int cut=n; cut>=1;){
			list.addFirst(cut);
			// sum=dp[cut].lastKey();
			sum=dp[cut].lowerKey(sum);
			cut=cuts[cut].get(sum);
		}
		// debug(list);
		list.removeLast();
		StringBuilder sb=new StringBuilder();
		sb.append(list.size()+1);
		sb.append('\n');
		for(int i : list){
			sb.append(i);
			sb.append(' ');
		}
		if(sb.charAt(sb.length()-1)==' '){
			sb.deleteCharAt(sb.length()-1);
		}
		println(sb.toString());
	}

	class Scanner{
		BufferedReader br;
		StringTokenizer st;

		Scanner(InputStream in){
			br=new BufferedReader(new InputStreamReader(in));
			eat("");
		}

		void eat(String s){
			st=new StringTokenizer(s);
		}

		String nextLine(){
			try{
				return br.readLine();
			}catch(IOException e){
				throw new IOError(e);
			}
		}

		boolean hasNext(){
			while(!st.hasMoreTokens()){
				String s=nextLine();
				if(s==null)
					return false;
				eat(s);
			}
			return true;
		}

		String next(){
			hasNext();
			return st.nextToken();
		}

		int nextInt(){
			return Integer.parseInt(next());
		}
	}

	void debug(Object... os){
		System.err.println(Arrays.deepToString(os));
	}

	void print(String s){
		System.out.print(s);
	}

	void println(String s){
		System.out.println(s);
	}

	public static void main(String[] args){
		new Main().run();
	}
}