AtCoderのABC
次のABCの問題へ
前のABCの問題へ
ABC252-D Distinct Trio
C#のソース(二分探索)
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("4");
WillReturn.Add("3 1 4 1");
//2
}
else if (InputPattern == "Input2") {
WillReturn.Add("10");
WillReturn.Add("99999 99998 99997 99996 99995 99994 99993 99992 99991 99990");
//120
}
else if (InputPattern == "Input3") {
WillReturn.Add("15");
WillReturn.Add("3 1 4 1 5 9 2 6 5 3 5 8 9 7 9");
//355
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
static void Main()
{
List<string> InputList = GetInputList();
int[] AArr = InputList[1].Split(' ').Select(pX => int.Parse(pX)).ToArray();
Array.Sort(AArr);
long Answer = 0;
int UB = AArr.GetUpperBound(0);
// 真ん中のIndを全探索
for (int I = 0; I <= UB; I++) {
int Ind_UpperBound = ExecNibunhou_UpperBound(AArr[I], AArr);
int Ind_LowerMax = ExecNibunhou_LowerMax(AArr[I], AArr);
if (Ind_UpperBound == -1) continue;
if (Ind_LowerMax == -1) continue;
Answer += (long)(UB - Ind_UpperBound + 1) * (Ind_LowerMax + 1);
}
Console.WriteLine(Answer);
}
// 二分法で、Val超えで最小の値を持つ、添字を返す
static int ExecNibunhou_UpperBound(int pVal, int[] pArr)
{
// 最後の要素がVal以下の特殊ケース
if (pVal >= pArr.Last()) {
return -1;
}
// 最初の要素がVal超えの特殊ケース
if (pVal < pArr[0]) {
return 0;
}
int L = 0;
int R = pArr.GetUpperBound(0);
while (L + 1 < R) {
int Mid = (L + R) / 2;
if (pArr[Mid] > pVal) {
R = Mid;
}
else {
L = Mid;
}
}
return R;
}
// 二分法で、Val未満で最大の値を持つ、添字を返す
static int ExecNibunhou_LowerMax(int pVal, int[] pArr)
{
// 最後の要素がVal未満の特殊ケース
if (pVal > pArr.Last()) {
return pArr.GetUpperBound(0);
}
// 最初の要素がVal以上の特殊ケース
if (pVal <= pArr[0]) {
return -1;
}
int L = 0;
int R = pArr.GetUpperBound(0);
while (L + 1 < R) {
int Mid = (L + R) / 2;
if (pArr[Mid] < pVal) {
L = Mid;
}
else {
R = Mid;
}
}
return L;
}
}
C#のソース(動的計画法)
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
return WillReturn;
}
static void Main()
{
List<string> InputList = GetInputList();
int[] AArr = InputList[1].Split(' ').Select(pX => int.Parse(pX)).ToArray();
// 度数分布表
var CntDict = new Dictionary<int, int>();
foreach (var Item in AArr.GroupBy(pX => pX)) {
CntDict[Item.Key] = Item.Count();
}
// 場合の数[個数]なDP表
long[] DPArr = new long[3];
DPArr[0] = 1;
long Answer = 0;
foreach (int EachVal in CntDict.Values) {
for (int I = 2; 0 <= I; I--) {
if (DPArr[I] == 0) continue;
int NewI = I + 1;
if (NewI == 3) {
Answer += DPArr[I] * EachVal;
continue;
}
DPArr[NewI] += DPArr[I] * EachVal;
}
}
Console.WriteLine(Answer);
}
}
C#のソース(余事象を使う)
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
return WillReturn;
}
static void Main()
{
List<string> InputList = GetInputList();
long[] AArr = InputList[1].Split(' ').Select(pX => long.Parse(pX)).ToArray();
// 度数分布表
var CntDict = new Dictionary<long, long>();
foreach (var Item in AArr.GroupBy(pX => pX)) {
CntDict[Item.Key] = Item.Count();
}
long Answer = 0;
// 全事象
Answer += DeriveChoose(AArr.Length, 3);
// 余事象1 同じ数字を2つ抽出
foreach (long EachVal in CntDict.Values) {
if (EachVal < 2) continue;
long MinusCnt = DeriveChoose(EachVal, 2);
MinusCnt *= AArr.Length - EachVal;
Answer -= MinusCnt;
}
// 余事象2 同じ数字を3つ抽出
foreach (long EachVal in CntDict.Values) {
if (EachVal < 3) continue;
long MinusCnt = DeriveChoose(EachVal, 3);
Answer -= MinusCnt;
}
Console.WriteLine(Answer);
}
// nCr を求める
static long DeriveChoose(long pN, long pR)
{
if (pN < pR) return 0;
pR = Math.Min(pR, pN - pR);
long WillReturn = 1;
for (long I = pN - pR + 1; I <= pN; I++) {
WillReturn *= I;
}
for (long I = 2; I <= pR; I++) {
WillReturn /= I;
}
return WillReturn;
}
}
解説
最初に配列をソートしておいて、
3つの数の真ん中を全探索して、
残りの2つは、二分探索して、積の法則を使ってます。
度数分布表を求めておいて、
場合の数[個数]を更新するDPで解くこともできます。
余事象を使って求めることもできます。